Get your 12 testers for Android, and security-check your app before Google reviews it
Google Play won't grant production access until 12 testers have been opted in to your closed test for 14 continuous days, and then it asks what those testers actually did. TestersWiz finds the testers and collects evidence that they installed and used your app. It also checks your APK for the leaked keys, risky settings and policy gaps that anyone who downloads it can find. Google alone decides production access.
You do not have to be accepted, and you do not have to finish a test. Even an application a developer turns down still counts.
- Install-proof screenshot at opt-in, reviewed by a person
- No cap on testers: recruit past 12 to cover drop-outs
- Security findings mapped to OWASP MASVS controls
- Your APK is never installed or run, and is deleted when the scan ends
- No ratings, reviews or incentivized installs, ever
By Syed Qadri · Last reviewed
Google Play's 12 testers requirement, precisely
If you created a personal developer account after 13 November 2023, you can't publish to production until you have run a closed test with at least 12 testers opted in, continuously, for 14 days. The full breakdown of the 12 testers, 14 days rule takes each word apart; this is the short version.
| What Google requires | |
|---|---|
| Who | Personal developer accounts created after 13 November 2023. Organization (D-U-N-S) accounts and older personal accounts are exempt. Play Console shows the account type in your account details. |
| Where | A closed testing track. Internal testing does not count. |
| How many | At least 12 opted-in testers. It was 20 until December 2024, which is why older posts still say 20. |
| How long | 14 continuous days at 12 or more. Days don't add up across gaps. |
| What counts | A tester who opted in through your track's opt-in link. |
| What doesn't | Being on the email list or in the Google Group without opting in; a sideloaded APK; installs from an internal track. |
| Then | You apply for production access from the Play Console Dashboard. Google says review usually takes seven days or less, but can take longer. |
The engagement trap: why 12 opt-ins isn't the finish line
Meeting the count lets you apply; it doesn't get you approved. The production access application asks three sets of questions:
1. About your closed test
How you recruited testers, how easy that was, how engaged they were, and a summary of their feedback.
2. About your app
Its intended audience, the value it provides, and expected installs in its first year.
3. About your production readiness
What you changed during the test, and how you decided the app was ready.
A developer who borrowed 12 inactive accounts has nothing true to write in the first or the third. That is the trap. How to answer the production access questions goes through each one.
What Google verifies
Google does not publish the signals it uses to judge engagement, and any page that lists them is guessing. The working assumption that holds up: Google runs the store, the track and the install, so it can see whether testers installed through Play and whether the app was used, and it has said it can refuse production access when testing looks insufficient. Plan for a test that would survive a person reading your answers.
The failures we see most
| Failure | Why it fails | Fix |
|---|---|---|
| Tester accepted the Google Group invite but never opted in | Group membership is not an opt-in | Send the opt-in link (Testing → Closed testing → your track → Testers), not the Group link |
| Tester installed an APK you sent them | A sideload is not a Google Play install | Remove it; install from the Play listing after opting in |
| Track not available in the tester's country | The opt-in page loads, but the app is not available to them | Closed testing → your track → Countries/regions: add them, or select all |
| Everyone joined on day 1 and went silent | The count is met, but there is nothing to report in section 1 | Collect feedback on a schedule, and ship at least one update from it |
| Count dipped to 11 on day 9 and nobody noticed | Continuity is broken | Recruit 14–16; check the count daily, not on day 14 |
To check that your own test device installed from Google Play rather than a sideload:
adb shell pm list packages -i com.your.app
# package:com.your.app installer=com.android.vending <- installed from Google Play
# installer=null, or a file manager <- sideloadedStep by step: from closed track to production access
Before day 0
- 1
Confirm the requirement applies to you (account type and creation date). Don't spend two weeks on a test you don't need.
- 2
Build the release you intend to ship: a signed AAB, the current target API level (API 36 for new apps and updates since 31 August 2026),
isMinifyEnabled = true, and not debuggable. - 3
Security-check that build now. Your testers install this exact binary, and anything in it is public from the first download. The checklist is below.
- 4
Create the closed track: Testing → Closed testing → create or edit a track → upload the AAB → release notes → roll out to the track. Closed releases are reviewed, so wait until the release is available to testers.
- 5
Set up testers: an email list, or a Google Group (
@googlegroups.com) people can join themselves. Set Countries/regions to include every tester. - 6
Copy the opt-in link:
https://play.google.com/apps/testing/com.your.app.
Days 0–14
- 7
Recruit 14–16 testers, not 12. On TestersWiz the listing keeps recruiting for the whole window, volunteers on a self-serve track get your Group and opt-in link straight away, each files an install-proof screenshot when they opt in, and you confirm each Gmail against your Play Console tester list before admitting them.
- 8
Watch the count daily, and top up the moment it drops toward 12.
- 9
Collect evidence as you go: screenshot check-ins (on TestersWiz, day 3), written feedback (from day 12), feedback through Google Play's testing feedback, and your own crash and ANR data in Android vitals.
- 10
Ship at least one update from that feedback. A new build does not reset the 14-day count, and it gives you a concrete answer to "what did you change?".
Day 14 onwards
- 11
Apply for production from the Dashboard. Answer with specifics: numbers, named fixes, quoted feedback. Answers that could describe any app tell Google nothing about yours.
- 12
Keep testers opted in until access is granted. The review can take a week.
- 13
Release with a staged rollout (for example 10% → 50% → 100%), and watch Android vitals between steps.
Security audit for your app: a MASVS checklist before closed testing
Why audit before the closed test, not after launch
- An APK is public from the first install. Your testers download the same binary the world will. Hardcoded keys, backend URLs and debug flags are readable with free tools in minutes, so a key leaked on day 0 of testing is already compromised.
- Some findings block the release, not just the risk: an outdated target API level; sensitive permissions such as
QUERY_ALL_PACKAGES, background location, SMS and call log, exact alarms or all-files access, which need a Play Console declaration; and a Data Safety form that doesn't match the SDKs in your build. - Fixing after testing means re-testing. A new build doesn't reset Google's clock, but a fix that changes behaviour deserves the testers' 14 days too.
A note on the standard. OWASP MASVS v2 has eight control groups: STORAGE, CRYPTO, AUTH, NETWORK, PLATFORM, CODE, RESILIENCE and PRIVACY. The old L1/L2 levels were removed from MASVS in v2.0; how deep to test now lives in the MASTG testing profiles. Obfuscation and tamper detection belong to MASVS-RESILIENCE, not MASVS-CODE.
Setup: turn your bundle into something you can inspect
# App Bundle -> universal APK (debug-signed, which is fine for inspection)
bundletool build-apks --bundle=app-release.aab --output=app.apks --mode=universal
unzip -p app.apks universal.apk > app.apk
aapt2 dump badging app.apk | grep -E "package:|sdkVersion|targetSdkVersion"
apkanalyzer manifest debuggable app.apk # must print: false
apkanalyzer manifest print app.apk > AndroidManifest.xml
jadx -d out/ app.apk # decompiled sources + resourcesMASVS-STORAGE: insecure data storage
| Check | How to verify | Fix |
|---|---|---|
Tokens or personal data in plaintext SharedPreferences, files or SQLite | On a debug build: adb shell run-as com.your.app ls shared_prefs databases files, then read the files | Encrypt with a Keystore-backed key (see MASVS-CRYPTO); keep session tokens in memory where you can |
| Secrets in logs | adb logcat --pid=$(adb shell pidof -s com.your.app), filtered for token, bearer and password | Strip logging from release builds, for example with a no-op logging tree or R8 -assumenosideeffects on Log |
| App data included in backups | android:allowBackup, android:fullBackupContent, and on Android 12+ android:dataExtractionRules | Exclude credential and token files from cloud backup and device transfer |
| Sensitive files on shared storage | Search for getExternalStorage and MediaStore writes of private data | Use app-specific internal storage |
Jetpack Security's EncryptedSharedPreferences was deprecated by Google in 2025. Existing code keeps working; for new code, use the Android Keystore directly (below) or Google's Tink library.
MASVS-CRYPTO: cryptography and key management
| Check | How to verify | Fix |
|---|---|---|
| ECB mode by default | Cipher.getInstance("AES") resolves to AES/ECB/PKCS5Padding on Android | AES/GCM/NoPadding with a Keystore key |
| Hardcoded keys and static IVs | SecretKeySpec( with literal bytes; IvParameterSpec(ByteArray(16)) | Generate keys in the Keystore and let the cipher create the IV |
| Weak hashes for passwords or integrity | MD5, SHA-1, DES, RC4 | Hash passwords on the server (Argon2 or bcrypt); SHA-256 or above for integrity |
| Predictable randomness | java.util.Random or kotlin.random.Random used for tokens | SecureRandom |
| Server secrets shipped in the app | Search the decompiled output for sk_live_, sk-, service_role, AKIA | Rotate first, then move the call behind your own backend. No obfuscation protects a secret shipped to the client |
val kg = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
kg.init(
KeyGenParameterSpec.Builder("session_key",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build()
)
val cipher = Cipher.getInstance("AES/GCM/NoPadding").apply { init(Cipher.ENCRYPT_MODE, kg.generateKey()) }
val iv = cipher.iv // generated by Keystore; store it with the ciphertextMASVS-NETWORK: network communication
| Check | How to verify | Fix |
|---|---|---|
| Cleartext HTTP allowed | android:usesCleartextTraffic="true" in the manifest, or cleartextTrafficPermitted="true" in network_security_config.xml. Cleartext is blocked by default from targetSdk 28 | Remove both; use HTTPS everywhere |
| Release build trusts user-installed CAs | <certificates src="user"/> outside <debug-overrides>. User CAs aren't trusted by default from targetSdk 24 | Keep user CAs in debug-overrides only |
| Broken TLS validation | An X509TrustManager with an empty checkServerTrusted, or a HostnameVerifier that returns true | Delete it and use the platform defaults |
| Dynamic confirmation | Proxy a release build through mitmproxy with its CA installed as a user certificate | If traffic decrypts, one of the rows above is wrong |
Certificate pinning is optional depth for high-risk apps. Done badly, it bricks your app at the next certificate rotation. If you pin, pin the SPKI, include a backup pin, and set an expiry.
<network-security-config>
<base-config cleartextTrafficPermitted="false" />
<domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<pin-set expiration="2027-06-30">
<pin digest="SHA-256">CURRENT_SPKI_BASE64=</pin>
<pin digest="SHA-256">BACKUP_SPKI_BASE64=</pin>
</pin-set>
</domain-config>
</network-security-config>openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null \
| openssl x509 -pubkey -noout | openssl pkey -pubin -outform der \
| openssl dgst -sha256 -binary | openssl enc -base64MASVS-PLATFORM: intents, exported components, deep links and WebViews
| Check | How to verify | Fix |
|---|---|---|
| Components other apps can open | exported="true" in the merged manifest. From targetSdk 31, every component with an intent filter must declare exported | exported="false" unless it must be public; protect public ones with a signature permission |
| Internal screens reachable directly | adb shell am start -n com.your.app/.internal.AdminActivity should fail as not exported | As above |
| Readable content providers | adb shell content query --uri content://com.your.app.provider/users | exported="false", grantUriPermissions scoped to specific paths, parameterized queries |
| Intent redirection | Code that launches an Intent taken from another intent's extras | Never forward untrusted intents; build a new explicit intent |
Mutable PendingIntent | PendingIntent.get* without FLAG_IMMUTABLE (mutability must be declared from targetSdk 31) | FLAG_IMMUTABLE unless mutation is genuinely needed |
| Deep links that act without checks | adb shell am start -W -a android.intent.action.VIEW -d "yourapp://reset?token=x&next=https://evil.example" com.your.app | Validate every parameter, allowlist redirect hosts, require sign-in before acting |
| App Links not verified | adb shell pm get-app-links com.your.app (Android 12+) | autoVerify="true" and a correct /.well-known/assetlinks.json |
| Unsafe WebView | addJavascriptInterface, setAllowFileAccess(true), setAllowUniversalAccessFromFileURLs(true), or URLs loaded from intents | Allowlist origins, disable file access, expose no bridge to untrusted content |
Over-broad FileProvider | <root-path> or path="." in file_paths.xml | Share specific directories only |
MASVS-CODE: code quality, dependencies and platform currency
| Check | How to verify | Fix |
|---|---|---|
| Debuggable release | apkanalyzer manifest debuggable app.apk | Never set debuggable on a release build type |
| Outdated target API | aapt2 dump badging → targetSdkVersion | Meet Google Play's current requirement |
| Known-vulnerable libraries | ./gradlew app:dependencies --configuration releaseRuntimeClasspath, then check versions against OSV or NVD (for example with OSV-Scanner on a Gradle lockfile) | Upgrade, and remove libraries you don't use |
| Injection through local queries | rawQuery( or execSQL( built by string concatenation | Room, or bound parameters |
MASVS-RESILIENCE: reverse engineering and tampering
| Check | How to verify | Fix |
|---|---|---|
| Code not shrunk or obfuscated | Class names in the jadx output are your real package names instead of a.b.c | Enable R8 for release builds (below). The Android Gradle plugin puts the mapping file in the AAB, so Play deobfuscates your crash reports |
| Tamper and device checks decided on the client | An integrity check whose result only the app reads | Use the Play Integrity API and verify the token on your server: appRecognitionVerdict == PLAY_RECOGNIZED, the device integrity labels and the licensing verdict. SafetyNet Attestation has been shut down |
Obfuscation slows analysis. It does not hide a key.
android {
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
}MASVS-PRIVACY: your Data Safety form
| Check | How to verify | Fix |
|---|---|---|
| Undeclared data collection | List every data-collecting SDK in your build: analytics, ads, crash reporting, attribution | Map each one to your Data Safety answers |
| Advertising ID added by a library | com.google.android.gms.permission.AD_ID in the merged manifest | Declare it, or remove it with tools:node="remove" if you don't use it |
| Permissions you don't use | The merged manifest lists permissions a library added | tools:node="remove" (in Expo, blockedPermissions) |
What an automated check covers, and what it can't
| Covered from the APK | Needs dynamic or manual testing |
|---|---|
| Leaked keys, including inside React Native and Flutter bundles | Authentication and session logic (MASVS-AUTH) |
| Manifest flags, exported components, cleartext and network-config settings | Your backend: Supabase RLS, Firebase rules, API authorization |
| Target API level, permissions that need a declaration | Runtime behaviour, actual traffic, business logic |
| Data-collecting SDKs, vulnerable library versions | Anything a penetration tester does by attacking the running system |
How TestersWiz fits: two tracks, one timeline
Before your closed test: the automated APK check
Upload your APK. It's analysed statically in an isolated environment with no internet access, never installed or run, and deleted when the scan finishes. It checks the Google Play launch checklist (target API, release build, permissions that may need a declaration, Data Safety), leaked keys, Supabase and Firebase setup, risky settings, data-collecting SDKs and known-vulnerable libraries. Your first check is free and tells you how many potential issues it found. The production-readiness report shows each one, with an OWASP MASVS control id where one applies, the fix, an AI prompt per finding, a Data Safety draft and a PDF.
During the 14 days: real testers, real evidence
Testers opt in through your Play link and file an install-proof screenshot, which a TestersWiz admin reviews. You confirm each Gmail against your own Play Console tester list. A day-3 screenshot check-in and a written feedback report (from day 12) give you material for the "about your closed test" and "production readiness" answers. Nobody is paid for installs, ratings or reviews.
What neither is: a penetration test, a certification, or a guarantee that your app or its backend is secure. For regulated apps, or anything handling payments or health data, commission a human assessment against the MASTG.
TestersWiz vs peer groups vs tester farms
| TestersWiz | Peer groups (Reddit / Discord) | Tester farms (bulk or bot accounts) | |
|---|---|---|---|
| Cost | Free to test and to list | Free | Paid |
| Real-device evidence | Install-proof screenshot at opt-in, reviewed by a person; a scheduled check-in; you verify each Gmail against Play Console | Honour system; you rarely know who actually opted in | Rarely offered; you can't inspect the accounts or devices |
| Staying opted in for 14 days | No one can guarantee it. You recruit past 12 with no cap, see drop-outs, and trades are fate-linked with deadlines and strikes | Goodwill only; drop-outs are invisible until your count falls | Often "guaranteed"; unverifiable, with no recourse beyond the vendor |
| Replacing drop-outs | The listing keeps recruiting for the whole window | Post again and hope | Depends on the vendor |
| Automated security check | Static APK check, first one free, findings mapped to OWASP MASVS | None | None |
| Feedback you can cite in the production application | Scheduled check-ins, a written report, in-app chat | Occasional comments | Usually none, or generic |
| What's promised about approval | Nothing: Google decides | Nothing | Some sell "guaranteed approval". No one can guarantee it |
| Risk to your developer account | Real people on their own Google accounts; no incentivized installs, ratings or reviews | Low, if the testers are real | Highest: accounts you can't vouch for, and no real usage to describe when Google asks |
Frequently asked questions
What is the Google Play 12 testers rule?
Personal Google Play developer accounts created after 13 November 2023 must run a closed test with at least 12 testers opted in for 14 continuous days before they can apply for production access. Organization accounts and older personal accounts are exempt. The number was 20 until Google lowered it to 12 in December 2024.
What happens if a tester drops out during the 14 days?
If your opted-in count falls below 12, the continuous 14-day period is broken, and you need 14 unbroken days at 12 or more before you apply. Recruit 14 to 16 testers so one or two drop-outs don't matter. On TestersWiz a listing keeps recruiting for its whole window with no cap on testers, so a replacement can join at any point.
How does Google verify if closed testers are real active users?
Google doesn't publish the signals it uses. What it does publish is the production access application, which asks how you recruited testers, how engaged they were and what feedback you received, and Google can refuse access if the testing looks insufficient. Plan for testers who genuinely install from Google Play, use the app and send feedback, because that is what you will have to describe.
Why do apps need a mobile app security audit before launching on the Play Store?
Everything inside an APK is readable by anyone who downloads it, including your closed testers, so a hardcoded secret key or a debuggable build is exposed from the first install. Issues Google checks for, such as an outdated target API level, undeclared sensitive permissions or a Data Safety form that doesn't match your SDKs, can also block your release. Fixing them before the closed test means the build your testers validate is the build you ship.
What does a mobile security audit check for?
A mobile app security assessment is usually organised around the OWASP MASVS categories: storage, cryptography, authentication, network, platform interaction, code, resilience and privacy. In practice that means leaked secrets, plaintext tokens, weak ciphers, cleartext traffic, exported components, unsafe deep links and WebViews, vulnerable libraries and missing R8 shrinking. An automated static check covers much of this from the APK; authentication, server-side logic and runtime behaviour need dynamic or manual testing.
Does TestersWiz guarantee Google Play production access?
No. Google alone decides who gets production access, so any service that guarantees approval is promising something it doesn't control. TestersWiz helps you meet the testing requirement with real testers and evidence you can cite in your application, and the security check helps you find problems before a reviewer or an attacker does.
Sources
- Play Console Help: app testing requirements for new personal developer accounts
- OWASP Mobile Application Security (MASVS and MASTG)
- Android Developers: network security configuration
- Android Developers: Play Integrity API
Google's requirements change. Dates and limits on this page were checked on 27 September 2026; Google's own pages are the authority.