| name | android-app-submission |
| description | End-to-end submission of a native Android app (AAB) to the Google Play Console for review and closed testing, driven through the Play Console web UI with Playwright MCP. Covers building/signing the release bundle, creating the app, store listing, ALL App content declarations (incl. Data safety for location), Store settings, creating a closed-testing track, adding the "All Testers" email list, selecting countries, uploading the AAB, and submitting for review. Includes the Misleading Claims (government-data source link) policy and a field-tested rejection-prevention checklist. Use when asked to "submit/publish an Android app", "push to Play Store", "closed test", or to fix a Play policy rejection. |
| license | MIT |
| metadata | {"version":"1.3.0"} |
Android App Submission — Play Console (closed testing)
Submit a native Android app to Google Play for review + closed testing. There is no
simple public API for first-time app setup without a Play Developer service account, so
this workflow drives the Play Console web UI with Playwright MCP. Everything below was
validated end-to-end on a real submission (and a real rejection + fix).
Quick recipe (the must-not-skip path)
- Build + sign the AAB (§0).
- Create app → Store listing → all 10 App content declarations → Store settings (§2–§5).
- Update
CHANGELOG.md for this versionName (versionCode) (§0.1) — its top entry is
the source of truth for the Play release notes.
- Closed testing track → Create release → upload AAB → release notes (from CHANGELOG) (§6.1).
- Testers tab → tick the "All Testers" email list → Save (§6.2). ← never skip; a track
with no tester list reaches no one.
- Countries/regions → add at least one (e.g. Singapore) → Save (§6.3).
- Submit for Closed testing: Release Review → Save → Go to overview →
Submit N changes for review → Send changes for review (§7).
The app's row in the app list stays "Draft" until the first release is approved; while
review is pending it reads "In review". That is expected — confirm success on the track
page (Release N (x.y) in review) and on Publishing overview ("Changes in review"), not the
Draft label.
Replace <DEV_ID> (developer account id) and <APP_ID> (numeric app id) in URLs with the
real values. The console base is
https://play.google.com/console/u/0/developers/<DEV_ID>/app/<APP_ID>/....
0. Prerequisites (build a signed AAB first)
- JDK 17+ (Android Studio bundles one at
/Applications/Android Studio.app/Contents/jbr/Contents/Home).
- Android SDK (set
ANDROID_HOME=$HOME/Library/Android/sdk), platform + build-tools 34/35.
- A release keystore (generate once, BACK IT UP — losing it means you can never update):
"$JAVA_HOME/bin/keytool" -genkeypair -v -keystore keystore/<app>-release.jks \
-alias <app> -keyalg RSA -keysize 2048 -validity 10000 \
-storepass <pw> -keypass <pw> \
-dname "CN=<Org>, O=<Org>, L=Singapore, C=SG"
Wire signing in app/build.gradle.kts from local.properties (NEVER commit keystore or
local.properties — gitignore them). Build:
export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
export ANDROID_HOME="$HOME/Library/Android/sdk"
./gradlew :app:bundleRelease
Verify it is signed: jarsigner -verify .../app-release.aab → "jar verified".
- Google Maps key (if used) in
local.properties as MAPS_API_KEY (manifest placeholder);
third-party data keys (e.g. LTA_DATAMALL_ACCOUNT_KEY) surfaced via BuildConfig.
- A privacy policy URL that is publicly reachable. Default to the company policy
https://www.tertiaryinfotech.com/privacy unless the user gives a different URL. If they
want an app-specific page instead, host a docs/privacy-policy.html via GitHub Pages
(gh api -X POST repos/<owner>/<repo>/pages -f "source[branch]=main" -f "source[path]=/docs")
and poll until HTTP 200 before using it.
0.1 Maintain a CHANGELOG.md (every release)
Keep a CHANGELOG.md at the repo root in Keep a Changelog
format. Every time you bump versionCode/versionName, add a new entry before building:
## [<versionName>] — <YYYY-MM-DD> · versionCode <N>
### Added / Changed / Fixed
- <user-facing notes>
### Play submission
- Track, testers (e.g. All Testers), countries, submission date; note any rejection + fix.
The newest entry's bullets are the single source of truth for the Play release notes
(§6.1) — copy them into the <en-US>…</en-US> block so the store, git history, and reviewers
all agree. Commit the CHANGELOG with the version bump.
1. Driving the console with Playwright MCP — gotchas
- Sticky footer intercepts clicks. The Save/Discard footer and asset side-panels
overlay buttons. When a normal click times out with "subtree intercepts pointer events",
click via JS instead:
browser_evaluate(() => document.querySelector('button[debug-id="..."]').click()).
- Asset uploads: click the Upload button via JS (
debug-id="upload-button"), then the
MCP file chooser opens → browser_file_upload([absolutePath]). After it lands in the
library it is auto-selected; click Add (debug-id="add-to-content-button") via JS.
- Material radios/checkboxes: programmatic
.click() usually works but aria-checked
reads can be stale right after — re-snapshot to confirm. Prefer Playwright nth() clicks
on input[type=checkbox] / [role=radio] when a label is empty in the a11y tree.
- Text fields bound to Angular forms: set them with real typing (
browser_type),
not by assigning .value in JS — programmatic value-set often does NOT register and the
field silently saves empty (this bit us on the contact email). Verify by reloading the page.
- Right URL for tracks: the track list is
…/app/<APP_ID>/closed-testing (NOT
…/tracks/closed-testing, which throws a transient "unexpected error"). From there
Manage track opens …/tracks/<TRACK_ID>.
2. Create the app
…/developers/<DEV_ID>/create-new-app → App name, package name (check availability),
default language, App, Free, tick both declarations (Program Policies + US export).
After "Create app", find the new id from the app list link …/app/<APP_ID>/app-dashboard.
3. Main store listing …/main-store-listing
App name, short description (≤80 chars), full description (≤4000). Upload:
- App icon 512×512 PNG (≤1 MB).
- Feature graphic 1024×500 PNG/JPG (no alpha — flatten with PIL if needed).
- 2–8 phone screenshots, 16:9 or 9:16, each side 320–3840 px (≥1080 px for promotion).
Real device captures are best; clean SVG→PNG mockups of the actual UI are accepted as a
starting point (
rsvg-convert/PIL at 1080×1920). Tablet screenshots are NOT required to save.
Misleading Claims trap (see §8): if the app surfaces government data, the full
description AND the app itself must carry a clear, clickable link to the official source.
4. App content declarations …/app-content/overview
All of these must be Actioned (10 items). Direct slugs:
privacy-policy, ads-declaration, testing-credentials (App access / "Sign in details"),
content-rating-overview, target-audience-content, data-privacy-security (Data safety),
ad-id-declaration, government-apps, finance, health.
- Privacy policy: paste the public URL — default
https://www.tertiaryinfotech.com/privacy unless the user specifies another.
- Ads: "No, my app does not contain ads" (unless it does).
- Sign in details / App access: "No" if nothing is gated behind login.
- Content rating: Start questionnaire → email + category All Other App Types + accept
IARC terms → answer No to every content question for a utility app → Save → Next → Save.
- Target audience: for an adult utility pick 18 and over only (skips the child-safety
sub-steps) → Save.
- Advertising ID: "No" if you don't use AD_ID (don't include
play-services-ads).
- Government apps / Financial features / Health: select "does not provide / not a
government app / no health features".
- Data safety (the long one): "Does your app collect or share required data types?" →
Yes if you read location (it is sent to Maps SDK/geocoder). Then:
- Encrypted in transit → Yes; account creation → "does not allow users to create an
account"; "Can users log in with external accounts?" → No; data deletion (optional) → No.
- Data types: check Approximate location + Precise location.
- Data usage → open each location type → Collected (not Shared) → processed
ephemerally: Yes → Users can choose (optional, since search-by-postcode works
without permission) → purpose App functionality → Save. Repeat for both.
- Preview → Save.
5. Store settings …/store-settings (a real publish blocker)
Set the App category (e.g. Maps & Navigation) AND Store listing contact details →
Email address (required). The dashboard "Provide app information" checklist will sit at
"10 of 11 complete" and the release will error "Your app cannot be published yet. Complete
the steps on the Dashboard" until BOTH are set. Type the email with real keystrokes and
reload to confirm it persisted (JS value-set silently fails here).
6. Closed testing track …/closed-testing → Manage track
- Create release → upload
app-release.aab (Play App Signing is on by default), set a
release name and release notes (<en-US>…</en-US>) — copy the bullets from the latest
CHANGELOG.md entry (§0.1) so the store and git history match.
- Testers tab → add the "All Testers" email list (REQUIRED every time).
- Open the track → Testers tab → under Email lists, tick the "All Testers" row.
The checkbox is a
mat-checkbox (class/aria-checked reads are unreliable) — verify by
screenshot that the box is filled blue, not by DOM scraping.
- Click Save (footer). Save then greys out = the list is committed to the track.
- If "All Testers" does not exist yet, Create email list: List name + the
"Add email addresses" field needs real typing + Enter to chip each address →
confirm Create → tick it → Save.
- You can tick more than one list; always include All Testers.
- Countries / regions tab → Add countries / regions → search & tick (e.g. Singapore,
or select-all) → Save.
7. Submit for Closed testing (send for review)
On the release Review step the only enabled action is Save (other unreviewed changes
are batched together). Then:
- Save → a dialog appears → Go to overview.
- On Publishing overview, click Submit N changes for review.
- Confirm Send changes for review in the dialog.
Pre-checks run (~14 min) then it goes to Google review (typically hours–7 days). Two warnings
are harmless: no deobfuscation file and no native debug symbols.
Verify it actually submitted (the app row still says "Draft" — that's fine):
- Closed testing track page →
Active · Release N (x.y) in review · 1 country / region.
- Publishing overview → Changes in review lists
Closed testing – <track> → N (x.y) Start full rollout.
After approval, share the opt-in URL (Closed testing → track → Testers) with testers; they
must accept before installing from Play.
8. Misleading Claims policy — government data (most common rejection here)
Symptom: Policy status → "App rejected — Misleading Claims policy" → detail:
"Missing Source Link for Government Information … your app provides government information but
lacks one or more clear and accessible URL/link(s) to the original source(s)." Evidence is
usually the Full description.
Why: LTA DataMall, data.gov.sg, OneMap, HDB, URA, MOH, etc. are government sources.
Any app surfacing that data — affiliated or not — must clearly cite the official source with
a working link, both in the listing and inside the app.
Fix (do all three):
- Full description — add an explicit line, e.g.
"Data source: Singapore Land Transport Authority (LTA) DataMall —
https://datamall.lta.gov.sg/. This app is not affiliated with or endorsed by LTA."
- In the app — show a tappable source link on a visible surface (an About tab is
ideal; see the companion
android-feedback-about skill) AND/or a small attribution on the
main screen. Link to the real source (e.g. https://datamall.lta.gov.sg/content/datamall/en.html).
- Bump versionCode, rebuild the AAB, create a new release on the track, and resubmit.
9. Rejection-prevention checklist (run BEFORE submitting)
Resume / iterate
The console autosaves drafts. To fix a rejection: make the change, bump versionCode,
./gradlew :app:bundleRelease, upload as a new release on the same track, and
Submit changes for review again from Publishing overview.