gplay-screenshot-automation
Google Play Screenshot Automation
Use this skill for agent-driven screenshot workflows where Android screenshots are captured via emulators or connected devices, organized by locale and device type, and uploaded to Google Play via gplay.
Current Scope
- Screenshot capture via
adb shell screencapand Android test frameworks (Espresso, UI Automator). - Multi-device capture: phone, tablet, TV, Wear OS.
- Multi-locale capture with emulator locale switching.
- Device framing with third-party tools.
- Upload via
gplay images uploadorgplay sync import-images. - CI/CD integration for fully automated pipelines.
Defaults
- Raw screenshots dir:
./screenshots/raw - Framed screenshots dir:
./screenshots/framed - Metadata dir (FastLane format):
./metadata
1) Emulator Setup
Create emulators for each device type
# Phone (Pixel 7, API 34)
sdkmanager "system-images;android-34;google_apis;x86_64"
avdmanager create avd \
--name "pixel7_api34" \
--device "pixel_7" \
--package "system-images;android-34;google_apis;x86_64"
# 10-inch Tablet
avdmanager create avd \
--name "tablet10_api34" \
--device "pixel_tablet" \
--package "system-images;android-34;google_apis;x86_64"
# 7-inch Tablet
avdmanager create avd \
--name "tablet7_api34" \
--device "Nexus 7" \
--package "system-images;android-34;google_apis;x86_64"
Boot emulators
emulator -avd pixel7_api34 -no-audio -no-window -gpu swiftshader_indirect &
adb wait-for-device
adb shell getprop sys.boot_completed # Wait until "1"
For headless CI environments, always use -no-window -no-audio -gpu swiftshader_indirect.
2) Basic Capture with adb
Single screenshot
adb shell screencap -p /sdcard/screenshot.png
adb pull /sdcard/screenshot.png ./screenshots/raw/en-US/phone/home.png
adb shell rm /sdcard/screenshot.png
Helper function for repeated captures
capture() {
local NAME="$1"
local LOCALE="$2"
local DEVICE_TYPE="$3"
local SERIAL="$4"
local OUTPUT_DIR="./screenshots/raw/$LOCALE/$DEVICE_TYPE"
mkdir -p "$OUTPUT_DIR"
adb -s "$SERIAL" shell screencap -p "/sdcard/$NAME.png"
adb -s "$SERIAL" pull "/sdcard/$NAME.png" "$OUTPUT_DIR/$NAME.png"
adb -s "$SERIAL" shell rm "/sdcard/$NAME.png"
echo "Captured $OUTPUT_DIR/$NAME.png"
}
# Usage
capture "home" "en-US" "phoneScreenshots" "emulator-5554"
capture "settings" "en-US" "phoneScreenshots" "emulator-5554"
capture "home" "en-US" "tenInchScreenshots" "emulator-5556"
3) Test Framework Capture (Espresso / UI Automator)
For repeatable, state-driven screenshots, use Android instrumentation tests.
Espresso screenshot test
// app/src/androidTest/java/com/example/app/ScreenshotTest.kt
@RunWith(AndroidJUnit4::class)
class ScreenshotTest {
@get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class.java)
@Test
fun captureHomeScreen() {
// Wait for content to load
onView(withId(R.id.main_content))
.check(matches(isDisplayed()))
takeScreenshot("home")
}
@Test
fun captureSearchScreen() {
onView(withId(R.id.search_button)).perform(click())
onView(withId(R.id.search_input)).perform(typeText("example"))
takeScreenshot("search")
}
private fun takeScreenshot(name: String) {
val bitmap = InstrumentationRegistry.getInstrumentation()
.uiAutomation.takeScreenshot()
val dir = File(
InstrumentationRegistry.getInstrumentation()
.targetContext.getExternalFilesDir(null),
"screenshots"
)
dir.mkdirs()
val file = File(dir, "$name.png")
file.outputStream().use {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
}
}
}
Run tests and pull screenshots
# Build and run instrumented tests
./gradlew connectedAndroidTest \
-Pandroid.testInstrumentationRunnerArguments.class=com.example.app.ScreenshotTest
# Pull screenshots from device
adb pull /sdcard/Android/data/com.example.app/files/screenshots/ ./screenshots/raw/en-US/phoneScreenshots/
UI Automator for cross-app flows
@RunWith(AndroidJUnit4::class)
class UiAutomatorScreenshotTest {
private lateinit var device: UiDevice
@Before
fun setup() {
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
}
@Test
fun captureNotificationScreen() {
device.openNotification()
device.wait(Until.hasObject(By.pkg("com.android.systemui")), 3000)
takeScreenshot("notifications")
}
private fun takeScreenshot(name: String) {
val file = File(
InstrumentationRegistry.getInstrumentation()
.targetContext.getExternalFilesDir(null),
"screenshots/$name.png"
)
file.parentFile?.mkdirs()
device.takeScreenshot(file)
}
}
4) Multi-locale Capture
Switch emulator locale via adb
set_locale() {
local SERIAL="$1"
local LOCALE="$2" # e.g. "de-DE"
local LANG="${LOCALE%%-*}" # e.g. "de"
local REGION="${LOCALE##*-}" # e.g. "DE"
adb -s "$SERIAL" shell "setprop persist.sys.locale ${LANG}-${REGION}"
adb -s "$SERIAL" shell "setprop persist.sys.language ${LANG}"
adb -s "$SERIAL" shell "setprop persist.sys.country ${REGION}"
adb -s "$SERIAL" shell "settings put system system_locales ${LANG}-${REGION}"
# Restart the app to pick up locale change
adb -s "$SERIAL" shell am force-stop com.example.app
adb -s "$SERIAL" shell am start -n com.example.app/.MainActivity
sleep 3
}
Capture across multiple locales
#!/bin/bash
# multi-locale-capture.sh
SERIAL="emulator-5554"
PACKAGE="com.example.app"
LOCALES=("en-US" "de-DE" "fr-FR" "es-ES" "ja" "ko" "pt-BR" "zh-CN")
for LOCALE in "${LOCALES[@]}"; do
echo "Capturing locale: $LOCALE"
set_locale "$SERIAL" "$LOCALE"
mkdir -p "./screenshots/raw/$LOCALE/phoneScreenshots"
# Capture each screen
for SCREEN in "home" "search" "settings" "profile"; do
adb -s "$SERIAL" shell screencap -p "/sdcard/$SCREEN.png"
adb -s "$SERIAL" pull "/sdcard/$SCREEN.png" \
"./screenshots/raw/$LOCALE/phoneScreenshots/$SCREEN.png"
adb -s "$SERIAL" shell rm "/sdcard/$SCREEN.png"
# Navigate to next screen (app-specific logic)
# adb -s "$SERIAL" shell input tap X Y
done
echo "Done: $LOCALE"
done
Using Espresso test arguments for locale
# Run screenshot tests with a specific locale
./gradlew connectedAndroidTest \
-Pandroid.testInstrumentationRunnerArguments.class=com.example.app.ScreenshotTest \
-Pandroid.testInstrumentationRunnerArguments.locale=de-DE
5) Multi-device Capture
Capture across phone, tablet, TV, and Wear
#!/bin/bash
# multi-device-capture.sh
declare -A DEVICES=(
["phoneScreenshots"]="emulator-5554" # Pixel 7
["tenInchScreenshots"]="emulator-5556" # Pixel Tablet
["sevenInchScreenshots"]="emulator-5558" # Nexus 7
["tvScreenshots"]="emulator-5560" # Android TV
["wearScreenshots"]="emulator-5562" # Wear OS
)
LOCALE="en-US"
for DEVICE_TYPE in "${!DEVICES[@]}"; do
SERIAL="${DEVICES[$DEVICE_TYPE]}"
echo "Capturing $DEVICE_TYPE on $SERIAL"
mkdir -p "./screenshots/raw/$LOCALE/$DEVICE_TYPE"
for SCREEN in "home" "search" "settings"; do
adb -s "$SERIAL" shell screencap -p "/sdcard/$SCREEN.png"
adb -s "$SERIAL" pull "/sdcard/$SCREEN.png" \
"./screenshots/raw/$LOCALE/$DEVICE_TYPE/$SCREEN.png"
adb -s "$SERIAL" shell rm "/sdcard/$SCREEN.png"
done
echo "Done: $DEVICE_TYPE"
done
6) Device Framing
Use third-party tools to wrap raw screenshots in device frames for polished store listings.
Using frameit (from fastlane)
gem install fastlane
# Place Framefile.json alongside screenshots
cat > ./screenshots/raw/Framefile.json << 'EOF'
{
"device_frame_version": "latest",
"default": {
"keyword": { "font": "./fonts/SF-Pro-Display-Bold.otf" },
"title": { "font": "./fonts/SF-Pro-Display-Regular.otf" }
}
}
EOF
cd ./screenshots/raw && fastlane frameit
Using a custom framing script
#!/bin/bash
# frame-screenshots.sh
# Requires ImageMagick
FRAME_IMAGE="./frames/pixel7_frame.png"
RAW_DIR="./screenshots/raw"
FRAMED_DIR="./screenshots/framed"
for LOCALE_DIR in "$RAW_DIR"/*/; do
LOCALE=$(basename "$LOCALE_DIR")
for TYPE_DIR in "$LOCALE_DIR"*/; do
TYPE=$(basename "$TYPE_DIR")
mkdir -p "$FRAMED_DIR/$LOCALE/$TYPE"
for IMG in "$TYPE_DIR"*.png; do
NAME=$(basename "$IMG")
convert "$FRAME_IMAGE" "$IMG" \
-gravity center -geometry +0+0 -composite \
"$FRAMED_DIR/$LOCALE/$TYPE/$NAME"
echo "Framed: $FRAMED_DIR/$LOCALE/$TYPE/$NAME"
done
done
done
7) Validate Before Upload
Always validate screenshots before uploading:
gplay validate screenshots --dir ./screenshots/framed --output table
Check specific locale:
gplay validate screenshots --dir ./screenshots/framed --locale en-US --output table
8) Upload to Google Play
Option A: Upload via sync (FastLane directory structure)
Organize screenshots in FastLane format:
metadata/
en-US/
images/
phoneScreenshots/
1_home.png
2_search.png
tenInchScreenshots/
1_home.png
de-DE/
images/
phoneScreenshots/
1_home.png
2_search.png
Then import:
gplay sync import-images \
--package com.example.app \
--dir ./metadata
Option B: Upload individual images
EDIT_ID=$(gplay edits create --package com.example.app | jq -r '.id')
# Upload phone screenshots
gplay images upload \
--package com.example.app \
--edit $EDIT_ID \
--locale en-US \
--type phoneScreenshots \
--file ./screenshots/framed/en-US/phoneScreenshots/home.png
gplay images upload \
--package com.example.app \
--edit $EDIT_ID \
--locale en-US \
--type phoneScreenshots \
--file ./screenshots/framed/en-US/phoneScreenshots/search.png
# Upload tablet screenshots
gplay images upload \
--package com.example.app \
--edit $EDIT_ID \
--locale en-US \
--type tenInchScreenshots \
--file ./screenshots/framed/en-US/tenInchScreenshots/home.png
# Upload feature graphic
gplay images upload \
--package com.example.app \
--edit $EDIT_ID \
--locale en-US \
--type featureGraphic \
--file ./screenshots/framed/en-US/featureGraphic.png
# Validate and commit
gplay edits validate --package com.example.app --edit $EDIT_ID
gplay edits commit --package com.example.app --edit $EDIT_ID
Option C: Upload as part of a release
gplay release \
--package com.example.app \
--track production \
--bundle app-release.aab \
--screenshots-dir ./metadata \
--release-notes @release-notes.json
Replace existing screenshots
Delete before uploading to replace:
EDIT_ID=$(gplay edits create --package com.example.app | jq -r '.id')
# Delete all existing phone screenshots for a locale
gplay images delete-all \
--package com.example.app \
--edit $EDIT_ID \
--locale en-US \
--type phoneScreenshots
# Upload new ones
gplay images upload \
--package com.example.app \
--edit $EDIT_ID \
--locale en-US \
--type phoneScreenshots \
--file ./screenshots/framed/en-US/phoneScreenshots/home.png
gplay edits validate --package com.example.app --edit $EDIT_ID
gplay edits commit --package com.example.app --edit $EDIT_ID
9) Full Automation Pipeline
#!/bin/bash
# screenshot-pipeline.sh
# End-to-end: boot emulators, capture, frame, validate, upload
PACKAGE="com.example.app"
SERIAL="emulator-5554"
LOCALES=("en-US" "de-DE" "fr-FR" "es-ES" "ja")
RAW_DIR="./screenshots/raw"
METADATA_DIR="./metadata"
# Step 1: Boot emulator
emulator -avd pixel7_api34 -no-audio -no-window -gpu swiftshader_indirect &
adb wait-for-device
adb -s "$SERIAL" shell "while [[ \$(getprop sys.boot_completed) != '1' ]]; do sleep 1; done"
# Step 2: Build and install app
./gradlew assembleRelease
adb -s "$SERIAL" install -r app/build/outputs/apk/release/app-release.apk
# Step 3: Capture per locale
for LOCALE in "${LOCALES[@]}"; do
set_locale "$SERIAL" "$LOCALE"
mkdir -p "$RAW_DIR/$LOCALE/phoneScreenshots"
for SCREEN in "home" "search" "settings" "profile"; do
adb -s "$SERIAL" shell screencap -p "/sdcard/$SCREEN.png"
adb -s "$SERIAL" pull "/sdcard/$SCREEN.png" "$RAW_DIR/$LOCALE/phoneScreenshots/$SCREEN.png"
adb -s "$SERIAL" shell rm "/sdcard/$SCREEN.png"
done
done
# Step 4: Organize into FastLane metadata structure
for LOCALE in "${LOCALES[@]}"; do
mkdir -p "$METADATA_DIR/$LOCALE/images/phoneScreenshots"
cp "$RAW_DIR/$LOCALE/phoneScreenshots/"*.png "$METADATA_DIR/$LOCALE/images/phoneScreenshots/"
done
# Step 5: Validate
gplay validate screenshots --dir "$METADATA_DIR" --output table
# Step 6: Upload
gplay sync import-images --package "$PACKAGE" --dir "$METADATA_DIR"
# Step 7: Kill emulator
adb -s "$SERIAL" emu kill
echo "Screenshot pipeline complete."
10) CI/CD Integration
GitHub Actions
name: Screenshot Pipeline
on:
workflow_dispatch:
schedule:
- cron: '0 4 * * 1' # Weekly Monday 4am
jobs:
screenshots:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: AVD cache
uses: actions/cache@v4
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-api-34
- name: Create AVD
run: |
sdkmanager "system-images;android-34;google_apis;x86_64"
avdmanager create avd -n pixel7_api34 -d pixel_7 \
--package "system-images;android-34;google_apis;x86_64" --force
- name: Build app
run: ./gradlew assembleRelease
- name: Capture screenshots
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
target: google_apis
arch: x86_64
profile: pixel_7
script: ./scripts/capture-screenshots.sh
- name: Validate screenshots
run: gplay validate screenshots --dir ./metadata --output table
- name: Upload to Play Store
run: |
gplay sync import-images \
--package ${{ secrets.PACKAGE_NAME }} \
--dir ./metadata
env:
GPLAY_SERVICE_ACCOUNT: ${{ secrets.GPLAY_SERVICE_ACCOUNT_PATH }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: screenshots
path: ./screenshots/
Google Play Image Type Reference
| Type | Usage |
|---|---|
phoneScreenshots |
Phone screenshots (required, 2-8) |
sevenInchScreenshots |
7-inch tablet screenshots |
tenInchScreenshots |
10-inch tablet screenshots |
tvScreenshots |
Android TV screenshots |
wearScreenshots |
Wear OS screenshots |
featureGraphic |
Feature graphic (1024x500) |
promoGraphic |
Promo graphic (180x120) |
icon |
App icon (512x512, usually set in Console) |
tvBanner |
TV banner (1280x720) |
Agent Behavior
- Always confirm exact flags with
--helpbefore running commands. - Use
gplay validate screenshotsbefore uploading. - Prefer
gplay sync import-imagesfor bulk uploads over individualgplay images uploadcalls. - When using individual uploads, always create an edit, upload, validate, then commit.
- Keep outputs deterministic: default to JSON for machine steps,
--output tablefor user review. - Use explicit long flags (
--package,--edit,--locale,--type,--file). - Verify emulator is fully booted (
sys.boot_completed == 1) before capturing. - For multi-locale workflows, always force-stop and relaunch the app after locale change.
- Validate screenshot counts (min 2 phone screenshots) before attempting upload.
Notes
- Google Play requires PNG or JPEG format; PNG is recommended for quality.
- Maximum image file size is 8 MB per screenshot.
- Screenshot ordering on Play Store matches upload order.
- Use
gplay images listto verify uploaded images. - Use
gplay images delete-allto clear screenshots before re-uploading. - Always use
--helpto verify flags for the exact command.
More from tamtom/gplay-cli-skills
gplay-cli-usage
Guidance for using the Google Play Console CLI in this repo (flags, output formats, pagination, auth, and discovery). Use when asked to run or design gplay commands or interact with Google Play Console via the CLI.
108gplay-gradle-build
Build, sign, and package Android apps with Gradle before uploading to Google Play. Use when asked to create an APK or AAB, configure signing, or set up build pipelines.
95gplay-submission-checks
Pre-submission validation for Google Play releases covering metadata, screenshots, bundle integrity, data safety, and policy compliance. Use when preparing a release to avoid rejections and catch issues before submitting.
93gplay-metadata-sync
Metadata and localization sync (including Fastlane format) for Google Play Store listings. Use when updating app descriptions, screenshots, or managing multi-locale metadata.
93gplay-signing-setup
Android app signing, keystores, and Play App Signing setup. Use when configuring signing for new apps or migrating to Play App Signing.
91gplay-iap-setup
In-app products, subscriptions, base plans, and offers setup for Google Play monetization. Use when configuring in-app purchases or subscription products.
89