gplay-screenshot-automation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Google Play Screenshot Automation

Google Play 截图自动化

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
.
本技能适用于由Agent驱动的截图工作流,可通过模拟器或连接的设备捕获Android截图,按语言区域和设备类型分类,并通过
gplay
上传至Google Play。

Current Scope

当前适用范围

  • Screenshot capture via
    adb shell screencap
    and 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 upload
    or
    gplay sync import-images
    .
  • CI/CD integration for fully automated pipelines.
  • 通过
    adb shell screencap
    和Android测试框架(Espresso、UI Automator)捕获截图。
  • 多设备捕获:手机、平板、电视、Wear OS设备。
  • 支持模拟器切换语言区域,实现多语言区域截图。
  • 借助第三方工具为截图添加设备框架。
  • 通过
    gplay images upload
    gplay sync import-images
    上传截图。
  • 与CI/CD集成,实现全自动化流水线。

Defaults

默认配置

  • Raw screenshots dir:
    ./screenshots/raw
  • Framed screenshots dir:
    ./screenshots/framed
  • Metadata dir (FastLane format):
    ./metadata
  • 原始截图目录:
    ./screenshots/raw
  • 带框架的截图目录:
    ./screenshots/framed
  • 元数据目录(FastLane格式):
    ./metadata

1) Emulator Setup

1) 模拟器设置

Create emulators for each device type

为每种设备类型创建模拟器

bash
undefined
bash
undefined

Phone (Pixel 7, API 34)

手机(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"
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

10英寸平板

avdmanager create avd
--name "tablet10_api34"
--device "pixel_tablet"
--package "system-images;android-34;google_apis;x86_64"
avdmanager create avd
--name "tablet10_api34"
--device "pixel_tablet"
--package "system-images;android-34;google_apis;x86_64"

7-inch Tablet

7英寸平板

avdmanager create avd
--name "tablet7_api34"
--device "Nexus 7"
--package "system-images;android-34;google_apis;x86_64"
undefined
avdmanager create avd
--name "tablet7_api34"
--device "Nexus 7"
--package "system-images;android-34;google_apis;x86_64"
undefined

Boot emulators

启动模拟器

bash
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
.
bash
emulator -avd pixel7_api34 -no-audio -no-window -gpu swiftshader_indirect &
adb wait-for-device
adb shell getprop sys.boot_completed  # 等待返回"1"
在无界面CI环境中,务必使用
-no-window -no-audio -gpu swiftshader_indirect
参数。

2) Basic Capture with adb

2) 借助adb实现基础截图

Single screenshot

单张截图

bash
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
bash
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

用于重复截图的辅助函数

bash
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"
}
bash
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"
undefined
capture "home" "en-US" "phoneScreenshots" "emulator-5554" capture "settings" "en-US" "phoneScreenshots" "emulator-5554" capture "home" "en-US" "tenInchScreenshots" "emulator-5556"
undefined

3) Test Framework Capture (Espresso / UI Automator)

3) 测试框架截图(Espresso / UI Automator)

For repeatable, state-driven screenshots, use Android instrumentation tests.
如需可重复、基于状态的截图,可使用Android插桩测试。

Espresso screenshot test

Espresso截图测试

kotlin
// 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)
        }
    }
}
kotlin
// 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() {
        // 等待内容加载完成
        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

运行测试并拉取截图

bash
undefined
bash
undefined

Build and run instrumented tests

构建并运行插桩测试

./gradlew connectedAndroidTest
-Pandroid.testInstrumentationRunnerArguments.class=com.example.app.ScreenshotTest
./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/
undefined
adb pull /sdcard/Android/data/com.example.app/files/screenshots/ ./screenshots/raw/en-US/phoneScreenshots/
undefined

UI Automator for cross-app flows

用于跨应用流程的UI Automator

kotlin
@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)
    }
}
kotlin
@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

4) 多语言区域截图

Switch emulator locale via adb

通过adb切换模拟器语言区域

bash
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
}
bash
set_locale() {
  local SERIAL="$1"
  local LOCALE="$2"   # 示例:"de-DE"
  local LANG="${LOCALE%%-*}"  # 示例:"de"
  local REGION="${LOCALE##*-}" # 示例:"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}"
  # 重启应用以应用语言区域变更
  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

跨多语言区域截图

bash
#!/bin/bash
bash
#!/bin/bash

multi-locale-capture.sh

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
undefined
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"

捕获每个页面

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"
# 导航至下一个页面(应用特定逻辑)
# adb -s "$SERIAL" shell input tap X Y
done
echo "Done: $LOCALE" done
undefined

Using Espresso test arguments for locale

借助Espresso测试参数指定语言区域

bash
undefined
bash
undefined

Run screenshot tests with a specific locale

使用指定语言区域运行截图测试

./gradlew connectedAndroidTest
-Pandroid.testInstrumentationRunnerArguments.class=com.example.app.ScreenshotTest
-Pandroid.testInstrumentationRunnerArguments.locale=de-DE
undefined
./gradlew connectedAndroidTest
-Pandroid.testInstrumentationRunnerArguments.class=com.example.app.ScreenshotTest
-Pandroid.testInstrumentationRunnerArguments.locale=de-DE
undefined

5) Multi-device Capture

5) 多设备截图

Capture across phone, tablet, TV, and Wear

跨手机、平板、电视和Wear OS设备截图

bash
#!/bin/bash
bash
#!/bin/bash

multi-device-capture.sh

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
undefined
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
undefined

6) Device Framing

6) 设备框架添加

Use third-party tools to wrap raw screenshots in device frames for polished store listings.
使用第三方工具为原始截图添加设备框架,打造更专业的商店列表展示图。

Using frameit (from fastlane)

使用frameit(来自fastlane)

bash
gem install fastlane
bash
gem install fastlane

Place Framefile.json alongside screenshots

在截图目录旁放置Framefile.json

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
undefined
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
undefined

Using a custom framing script

使用自定义框架脚本

bash
#!/bin/bash
bash
#!/bin/bash

frame-screenshots.sh

frame-screenshots.sh

Requires ImageMagick

依赖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
undefined
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
undefined

7) Validate Before Upload

7) 上传前验证

Always validate screenshots before uploading:
bash
gplay validate screenshots --dir ./screenshots/framed --output table
Check specific locale:
bash
gplay validate screenshots --dir ./screenshots/framed --locale en-US --output table
上传前务必验证截图:
bash
gplay validate screenshots --dir ./screenshots/framed --output table
检查特定语言区域:
bash
gplay validate screenshots --dir ./screenshots/framed --locale en-US --output table

8) Upload to Google Play

8) 上传至Google Play

Option A: Upload via sync (FastLane directory structure)

选项A:通过同步上传(FastLane目录结构)

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:
bash
gplay sync import-images \
  --package com.example.app \
  --dir ./metadata
按FastLane格式组织截图:
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
然后导入:
bash
gplay sync import-images \
  --package com.example.app \
  --dir ./metadata

Option B: Upload individual images

选项B:上传单张图片

bash
EDIT_ID=$(gplay edits create --package com.example.app | jq -r '.id')
bash
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
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
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
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
undefined
gplay edits validate --package com.example.app --edit $EDIT_ID gplay edits commit --package com.example.app --edit $EDIT_ID
undefined

Option C: Upload as part of a release

选项C:作为版本发布的一部分上传

bash
gplay release \
  --package com.example.app \
  --track production \
  --bundle app-release.aab \
  --screenshots-dir ./metadata \
  --release-notes @release-notes.json
bash
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:
bash
EDIT_ID=$(gplay edits create --package com.example.app | jq -r '.id')
上传前删除原有截图以实现替换:
bash
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
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
undefined
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
undefined

9) Full Automation Pipeline

9) 全自动化流水线

bash
#!/bin/bash
bash
#!/bin/bash

screenshot-pipeline.sh

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"
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

步骤1:启动模拟器

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"
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

步骤2:构建并安装应用

./gradlew assembleRelease adb -s "$SERIAL" install -r app/build/outputs/apk/release/app-release.apk
./gradlew assembleRelease adb -s "$SERIAL" install -r app/build/outputs/apk/release/app-release.apk

Step 3: Capture per locale

步骤3:按语言区域截图

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
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

步骤4:整理为FastLane元数据结构

for LOCALE in "${LOCALES[@]}"; do mkdir -p "$METADATA_DIR/$LOCALE/images/phoneScreenshots" cp "$RAW_DIR/$LOCALE/phoneScreenshots/"*.png "$METADATA_DIR/$LOCALE/images/phoneScreenshots/" done
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

步骤5:验证截图

gplay validate screenshots --dir "$METADATA_DIR" --output table
gplay validate screenshots --dir "$METADATA_DIR" --output table

Step 6: Upload

步骤6:上传至商店

gplay sync import-images --package "$PACKAGE" --dir "$METADATA_DIR"
gplay sync import-images --package "$PACKAGE" --dir "$METADATA_DIR"

Step 7: Kill emulator

步骤7:关闭模拟器

adb -s "$SERIAL" emu kill
echo "Screenshot pipeline complete."
undefined
adb -s "$SERIAL" emu kill
echo "Screenshot pipeline complete."
undefined

10) CI/CD Integration

10) CI/CD集成

GitHub Actions

GitHub Actions

yaml
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/
yaml
name: Screenshot Pipeline
on:
  workflow_dispatch:
  schedule:
    - cron: '0 4 * * 1'  # 每周一凌晨4点执行

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缓存
        uses: actions/cache@v4
        with:
          path: |
            ~/.android/avd/*
            ~/.android/adb*
          key: avd-api-34

      - name: 创建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: 构建应用
        run: ./gradlew assembleRelease

      - name: 捕获截图
        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: 验证截图
        run: gplay validate screenshots --dir ./metadata --output table

      - name: 上传至Play商店
        run: |
          gplay sync import-images \
            --package ${{ secrets.PACKAGE_NAME }} \
            --dir ./metadata
        env:
          GPLAY_SERVICE_ACCOUNT: ${{ secrets.GPLAY_SERVICE_ACCOUNT_PATH }}

      - name: 上传产物
        uses: actions/upload-artifact@v4
        with:
          name: screenshots
          path: ./screenshots/

Google Play Image Type Reference

Google Play图片类型参考

TypeUsage
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)
类型用途
phoneScreenshots
手机截图(必填,需2-8张)
sevenInchScreenshots
7英寸平板截图
tenInchScreenshots
10英寸平板截图
tvScreenshots
Android TV截图
wearScreenshots
Wear OS截图
featureGraphic
宣传图(1024x500)
promoGraphic
促销图(180x120)
icon
应用图标(512x512,通常在控制台设置)
tvBanner
TV横幅图(1280x720)

Agent Behavior

Agent行为规范

  • Always confirm exact flags with
    --help
    before running commands.
  • Use
    gplay validate screenshots
    before uploading.
  • Prefer
    gplay sync import-images
    for bulk uploads over individual
    gplay images upload
    calls.
  • When using individual uploads, always create an edit, upload, validate, then commit.
  • Keep outputs deterministic: default to JSON for machine steps,
    --output table
    for 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.
  • 运行命令前务必通过
    --help
    确认参数。
  • 上传前使用
    gplay validate screenshots
    验证截图。
  • 批量上传优先使用
    gplay sync import-images
    ,而非多次调用
    gplay images upload
  • 单独上传时,务必先创建编辑任务、上传截图、验证,再提交。
  • 保持输出确定性:机器执行步骤默认使用JSON格式,用户查看时使用
    --output table
  • 使用明确的长参数(
    --package
    ,
    --edit
    ,
    --locale
    ,
    --type
    ,
    --file
    )。
  • 截图前验证模拟器是否完全启动(
    sys.boot_completed == 1
    )。
  • 多语言区域工作流中,变更语言区域后务必强制停止并重启应用。
  • 上传前验证截图数量(手机截图至少2张)。

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 list
    to verify uploaded images.
  • Use
    gplay images delete-all
    to clear screenshots before re-uploading.
  • Always use
    --help
    to verify flags for the exact command.
  • Google Play要求截图为PNG或JPEG格式;推荐使用PNG以保证画质。
  • 单张截图最大文件大小为8MB。
  • Play商店中的截图顺序与上传顺序一致。
  • 使用
    gplay images list
    验证已上传的图片。
  • 重新上传前可使用
    gplay images delete-all
    清空现有截图。
  • 务必使用
    --help
    确认命令的准确参数。