商店截图
目的
生成能够转化用户、忠实于应用且适合商店每个槽位的截图,通过脚本实现,以便 UI 变更时能在几分钟内重新生成。
前提条件
- 构建版本中 UI 已为此版本最终冻结。
- 应用中包含捕获框架(Flutter golden/集成测试、XCUITest、Espresso/Compose 截图测试或 Fastlane snapshot),能够使用确定性演示数据渲染屏幕。
- 已安装 Node.js 18+ 和 Chrome/Chromium 用于渲染(在
scripts/目录中执行npm i)。 - 已获取用于上传的 API 凭据(
accounts-and-tax)。
原则
- 真实捕获,而非模拟图。 每张手机屏幕内的画面都是已发布应用的实际捕获。重绘或“改进”的 UI 会误导用户(准则 2.3),且容易过时。
- 数字必须与应用一致。 如果画面显示“1,850 步”或“12 分钟”,应用必须能为演示数据精确生成这些值。在框架中生成演示数据,并在同一测试中断言。
- 如果截图显示了应用不支持的内容,请修复其中之一。 要么构建该功能,要么更改画面。永远不要带着这种差异发布。
- 画面中不得出现其他平台的元素。 不要在 iOS 上显示 Android 状态/导航栏,也不要在 Play 上显示苹果 UI 或“App Store”(2.3.10)。
- 在所有语言环境和设备上保持相同的画面顺序。
设备矩阵
完整表格(含逻辑尺寸和 API 标识符):references/device-matrix.md。摘要(请验证 Apple 截图规格 和 Play 预览资源):
| 槽位 | 像素(竖屏) | 是否必需? |
|---|---|---|
| iPhone 大 Dynamic Island(6.9") | 1320×2868(也支持 1290×2796、1260×2736) | 推荐主集 |
| iPhone 中等 Dynamic Island | 1206×2622 或 1179×2556 | 至少需要一张截图 |
| iPhone Duo 外屏 | 1398×2034 | 从 2027 年 4 月起,对于使用 iOS 27.1 SDK 或更高版本构建的应用必需;添加“针对 iPhone Duo 优化”徽章 |
| iPhone Duo 内屏 | 2007×2853 | 与外屏相同 |
| iPad 13" | 2064×2752(或 2048×2732) | 仅当应用在 iPad 上运行时必需(不适用于仅限 iPhone 的应用) |
| Play 手机 | 例如 1080×1920 或 1080×2160(边长 320–3840 像素,长边 ≤ 短边 × 2) | ≥ 2 张截图才能发布;≥ 4 张且分辨率 ≥ 1080 px 更佳 |
| Play 7" 和 10" 平板 | 1080–7680 像素,9:16 或 16:9 | 如果应用支持平板,则推荐 |
| Play 功能图形 | 1024×500 | 必需 |
| Play 图标 | 512×512,32 位 PNG | 必需 |
Apple:每个尺寸最多 10 张;PNG 或 JPEG;无 Alpha 通道或透明度。Play:每种设备类型最多 8 张;JPEG 或 24 位 PNG(无 Alpha)。
App Store 产品页面创意资源(较新的 App Store Connect,“标题和搜索结果”选项卡):
- 标题:产品页面顶部的一张宽图,尺寸为 3840×1646 或 5244×2950。可选(不提交也不影响审核通过)。
- 搜索结果:尺寸为 5244×2950、3840×2560 或 1920×1280;如果未设置,则使用截图和预览。
- 通过版本页面或资源库(上传创意资源)上传,然后通过“浏览资源”选取。上传到资源库的资源可后续复用。
- 设计说明:商店会将应用图标、名称和获取按钮叠加在标题图片的一部分上,因此请在控制台预览中检查效果。
- 这些槽位在版本状态为“等待审核”时会被锁定。请在提交之前添加它们。
操作步骤
1. 规划故事(需要审批故事板)
编写一个包含 5–8 个画面的故事板:一个标题(≤ 6 个词)、一行辅助说明、一个捕获画面。可以选择“全景”布局(即捆绑模板所做的):画面是同一宽画布的一部分,因此形状会跨画面边缘延续。无论采用何种布局,任何展示的优惠(免费试用、试用期)都必须是真实的。
2. 捕获真实屏幕
以目标设备的逻辑尺寸 × 像素比捕获每个屏幕,确保文本和布局与该设备实际显示完全一致。Flutter 示例(widget/golden 测试):
// 6.9" iPhone:440 x 956 逻辑点 @3x = 1320 x 2868 像素
tester.view.physicalSize = const Size(1320, 2868);
tester.view.devicePixelRatio = 3.0;
await tester.pumpWidget(App(demo: DemoData.storeShots(locale: 'en')));
await tester.pumpAndSettle();
expect(find.text('1,850 steps'), findsOneWidget); // 标题中的数字是真实的
await expectLater(find.byType(App), matchesGoldenFile('captures/en/iphone69/01_home.png'));
- 在测试中加载字体(否则测试字体会显示为方框)。
- 使用固定的时钟、固定的演示数据,不依赖网络。
- 隐藏调试横幅;将状态栏设置为干净状态(或裁剪掉并完全不绘制)。
- 分别捕获每个语言环境;切勿在本地化画面中放置英文 UI。
3. 合成画面
模板:templates/panorama.html,渲染器:scripts/render.mjs。
cd scripts && npm i
node render.mjs ../templates/frames.example.json out/
渲染器将所有画面放置在同一画布上(宽度 = 画面数量 × 画面宽度),加载捕获图像,等待字体加载,然后为配置中的每个设备以精确像素尺寸截图每个画面切片。它还渲染 1024×500 的功能图形。
4. 检查
node check-images.mjs out/
在以下情况下失败:图片尺寸与所在文件夹的设备不匹配、存在 Alpha 通道(PNG 颜色类型 4/6 或 tRNS 块)、文件数量超过 10 个(Apple)/ 8 个(Play)、编号存在空缺。然后以 100% 缩放查看每张图片:检查是否有截断的文本、其他平台的 UI、占位符字符串、过时的数字。
5. 通过 API 上传(替换在线截图集前需要审批)
- App Store Connect — scripts/upload-asc.mjs:找到可编辑的
appStoreVersion→ 其appStoreVersionLocalizations→ 每个screenshotDisplayType对应的appScreenshotSets→ 删除旧截图 → 预留(POST /v1/appScreenshots,参数为fileName、fileSize)→ 从uploadOperations上传各部分 → 提交(PATCH,设置uploaded: true和 MD5sourceFileChecksum)→ 设置集合appScreenshots关系的顺序。文档:上传资源到 App Store Connect。 - Google Play — scripts/upload-play.mjs:
edits.insert→edits.images.deleteall(按语言和图片类型)→edits.images.upload按顺序(phoneScreenshots、sevenInchScreenshots、tenInchScreenshots、featureGraphic)→edits.commit。文档:edits.images.upload。
两个脚本默认执行干运行;设置 DRY_RUN=0 以实际写入。
6. 回读验证
按语言环境和设备列出已上传的资源(两个 API),并将数量和顺序与本地文件夹进行比较。Apple 异步处理上传;请等待直到每个资源报告 COMPLETE 传递状态。
需要人工审批的决策点
故事板和标题、最终视觉方向、替换正在使用的截图集、提交 Play 编辑。
验证
check-images.mjs通过;每个必需的 Apple 槽位至少有一张图片;Play 有手机截图和功能图形。- 回读顺序一致;无失败资源状态。
- 画面中的每个数字和功能都已对照构建版本检查过。
常见陷阱
- 只上传 6.9" 截图而遗漏了必需的中等 Dynamic Island 槽位。
- 导出的 PNG 包含 Alpha 通道(常见于设计工具和无头浏览器使用透明背景的情况——务必设置不透明的主体背景)。
- 将 6.9" 的 1320×2868 图片用于 Play:其宽高比超出 Play 的 2:1 限制;请渲染 Play 专用尺寸。
- 为仅限 iPhone 的应用制作 iPad 截图(不需要)——或者当应用确实在 iPad 上运行时却缺少这些截图。
- 画面显示了应用不存在的设置行或结果。
- 标题已翻译但捕获画面仍为英文。