How to Test Avatar Upload on Android (Complete Guide)
Avatar upload is a deceptively simple feature that touches many parts of an Android app: UI components that accept image input, permission handling, background services that resize or compress images,
Why Avatar Upload Deserves Focused Testing
Avatar upload is a deceptively simple feature that touches many parts of an Android app: UI components that accept image input, permission handling, background services that resize or compress images, network calls that send multipart payloads, storage logic that persists the result, and UI updates that reflect the new picture. Because the flow involves user‑generated media, it is a frequent source of crashes, ANRs, silent failures, and privacy leaks. In production, a broken avatar upload can prevent users from completing onboarding, trigger GDPR‑relevant data exposure, or cause the app to appear unpolished when the placeholder image never changes. Treating it as a “nice‑to‑have” widget leads to under‑tested code paths that surface only after a release, when users encounter odd behavior such as the picker closing without feedback, the upload stalling at 0 %, or the server rejecting a file because of an unexpected MIME type. A dedicated test effort catches these issues early, reduces support tickets, and protects the brand image that the avatar itself represents.
Common Failure Modes Seen in the Wild
Before diving into test design, it helps to catalog the ways avatar upload breaks after it leaves the QA environment. The list below is drawn from real‑world crash reports, ANR traces, and user complaints collected from several Android apps that expose a profile picture picker.
| Failure Category | Typical Symptom | Root Cause (examples) |
|---|---|---|
| Permission mishandling | Picker never opens; toast says “Permission denied” | Missing runtime request for READ_EXTERNAL_STORAGE or CAMERA on Android 13+; using legacy manifest‑only permission |
| Picker integration bugs | Chooser shows duplicate entries or crashes when selecting from Google Photos | Improper Intent flags (missing Intent.FLAG_GRANT_READ_URI_PERMISSION) |
| Image processing OOM | App crashes with OutOfMemoryError after selecting a 12 MP photo | Decoding bitmap at original size before down‑sampling; not using BitmapFactory.Options.inSampleSize |
| Upload stalls | Progress bar stays at 0 % or 100 % indefinitely | Network timeout not handled; missing retry logic; server returns 413 but client treats as success |
| Incorrect MIME/type | Server rejects file with “Unsupported media type” | Using ContentResolver.getType() on a Uri that returns null; falling back to image/jpeg for HEIC files |
| Silent failure | UI shows old avatar; no error toast | Swallowing exceptions in AsyncTask/Worker; not observing LiveData updates |
| Accessibility gaps | TalkBack does not announce success/failure | Missing contentDescription on buttons; not using announceForAccessibility |
| Security/privacy leaks | Selected image appears in app’s external cache readable by other apps | Storing raw image to getExternalCacheDir() without setPrivate(); logging image paths |
| Rotation/orientation loss | Uploaded avatar appears sideways | Not honoring Exif orientation matrix before encoding to JPEG |
| Duplicate uploads | Same image sent multiple times on rapid taps | No debouncing; multiple click listeners firing |
These patterns show that avatar upload is not just a UI test; it intersects with permissions, media handling, networking, concurrency, and accessibility. A test matrix that addresses each dimension is essential.
Comprehensive Test Matrix for Avatar Upload
The following table expands the failure categories into concrete test cases. Each row includes a short description, the expected outcome, and the severity level (P1 = blocker, P2 = high, P3 = medium). Use this matrix as a checklist when designing both manual and automated tests.
| ID | Category | Test Case Description | Steps (high‑level) | Expected Result | Severity |
|---|---|---|---|---|---|
| A1 | Happy path – Gallery | Select a JPEG from device gallery, crop if needed, confirm upload | Open profile → tap avatar → choose “Gallery” → navigate to image → confirm → wait for upload success toast | Avatar updates instantly; no error messages; network log shows 200 OK with correct multipart payload | P1 |
| A2 | Happy path – Camera | Capture a photo with camera, use it as avatar | Same as A1 but choose “Camera” → take picture → accept → upload | Avatar updates; image is correctly oriented; no permission dialog after grant | P1 |
| B1 | Permission denied – Storage | Deny READ_EXTERNAL_STORAGE when picker launches | Deny permission via app settings → attempt gallery picker | Picker shows system dialog to grant permission; after denial, app shows rationale toast and does not crash | P2 |
| B2 | Permission denied – Camera | Deny CAMERA permission before taking picture | Deny CAMERA → attempt camera picker | Same as B1 but for camera; app handles gracefully | P2 |
| C1 | Large image OOM | Select a 20 MP photo (≈10 MB) from gallery | Pick largest available image → confirm | App downsamples before decoding; no OOM; upload succeeds (may be slower) | P1 |
| C2 | Zero‑byte file | Attempt to upload a file with size 0 B (create empty file via adb) | adb shell touch /sdcard/empty.jpg → select via picker → confirm | App shows error toast “Invalid image”; no crash | P2 |
| C3 | Unsupported format (HEIC) | Choose a HEIC image (common on newer devices) | Transfer HEIC file to device → select → confirm | App either converts to JPEG/PNG or shows clear unsupported‑type message | P2 |
| D1 | Network loss mid‑upload | Enable airplane mode after selecting image but before upload completes | Start upload → toggle airplane mode → wait | App shows retry mechanism or error toast; does not hang; partial upload cleaned up | P1 |
| D2 | Server returns 413 Payload Too Large | Mock server to reject oversized image | Use a tool like mitmproxy to return 413 → select large image → confirm | App displays “File too large” toast; does not crash; allows user to pick smaller image | P2 |
| D3 | Invalid MIME from ContentResolver | Provide a Uri with null MIME type (e.g., file://) | Use content:// scheme pointing to a non‑media file → select → confirm | App falls back to extension‑based detection or shows error; no NPE | P2 |
| E1 | Accessibility – TalkBack announcement | Verify TalkBack reads success/failure | Enable TalkBack → perform A1 or C3 → listen | TalkBack announces “Avatar updated” or “Upload failed, please try again” | P2 |
| E2 | Touch target size | Ensure avatar button meets 48 dp minimum | Use UI Automator to get bounds → verify ≥48 dp | Passes Android accessibility guideline | P2 |
| E3 | Color contrast | Check avatar placeholder/icon contrast against background | Use accessibility scanner → verify contrast ratio ≥4.5:1 | Passes WCAG AA | P2 |
| F1 | Private storage leakage | Verify selected image not written to world‑readable location | After upload, inspect /sdcard/Android/data/ and external storage | No raw image file outside app‑private directories | P1 |
| F2 | Image metadata stripping | Confirm EXIF GPS data removed before upload | Pick geotagged photo → intercept network call → check payload | No GPS tags in uploaded multipart body | P2 |
| G1 | Rapid double tap | Tap avatar button twice quickly | Two taps within 200 ms → observe | Only one picker launches; second tap ignored or shows “Already picking” toast | P2 |
| G2 | Orientation change mid‑flow | Rotate device while picker is open | Start picker → rotate to landscape → confirm selection | Picker dismisses gracefully or retains state; no crash | P2 |
| H1 | Cross‑profile user flow | Test avatar upload for a newly created account vs. existing account | Create new account → go to profile → upload avatar → log out → log in → verify avatar persists | Avatar persists across sessions; no mismatch | P1 |
| H2 | Data cleanup on logout | Ensure avatar image removed from private cache on logout | Upload avatar → log out → check cache directory | Avatar file deleted or overwritten with default placeholder | P2 |
How to Use the Matrix
- Manual testing: Walk through each ID, marking pass/fail.
- Automated testing: Map IDs to test methods; use parameterized runners for similar cases (e.g., B1/B2 share permission‑handling logic).
- Risk‑based prioritization: Focus first on P1 items; schedule P2/P3 for each sprint cycle.
Manual Testing Approach – Step‑by‑Step
Even with automation, a manual exploratory pass catches nuances that scripts may miss, especially around device‑specific UI quirks and permission dialogs. Below is a reproducible manual procedure that you can follow on any Android device or emulator.
- Environment preparation
- Install the app under test (debug or release variant).
- Clear app data (
adb shell pm clear) to start from a clean state. - Grant or revoke permissions as needed via
adb shell pm grantorandroid.permission.READ_EXTERNAL_STORAGE revoke. - Optionally set up a proxy (e.g.,
mitmproxy) to capture network calls and inject error responses.
- Baseline happy path
- Launch the app, navigate to the profile screen.
- Tap the avatar placeholder.
- Choose Gallery from the chooser.
- Use the system picker to select a JPEG image of moderate size (≈500 KB).
- If a cropping UI appears, accept the default crop.
- Observe a success toast or snackbar.
- Verify the new avatar appears instantly in the UI.
- Check network logs: a
POST /api/avatarwithContent-Type: multipart/form-dataand a part namedavatarcontaining the image bytes.
- Permission denial scenarios
- Revoke
READ_EXTERNAL_STORAGE. - Repeat step 2; expect a system permission dialog.
- Deny the request; verify the app shows a user‑friendly explanation and does not crash.
- Grant the permission via settings and repeat; the flow should succeed.
- Camera path
- Grant
CAMERApermission. - Choose Camera from the chooser.
- Take a picture, confirm use.
- Verify orientation (portrait/landscape) matches device rotation.
- Large file & OOM test
- Transfer a high‑resolution image (>15 MP) to
/sdcard/Pictures/. - Select it via gallery.
- Watch logcat for
BitmapFactorydecoding; ensure noOutOfMemoryError. - Confirm upload succeeds (may take longer).
- Error injection
- Using
mitmproxy, configure a rule to return HTTP 413 for/api/avatar. - Attempt upload with any valid image.
- Confirm the app displays an appropriate error and allows a retry.
- Accessibility check
- Enable TalkBack (
Settings → Accessibility → TalkBack). - Perform the happy‑path upload.
- Listen for spoken feedback after success and after any error you inject.
- Use Accessibility Scanner to verify touch target sizes and contrast.
- Privacy & storage verification
- After a successful upload, run
adb shell ls -l /sdcard/and look for any leftover.jpgor.pngfiles bearing the original name or timestamps. - Check app‑private cache:
adb shell run-as.ls /data/data/ /cache - Ensure no raw image resides outside these directories.
- Stress & concurrency
- Rapidly tap the avatar button 5‑10 times within a second.
- Observe that only one picker opens; subsequent taps are ignored or show a brief toast.
- Rotate the device while the picker is open; confirm no crash and that the picker either dismisses or retains state correctly.
- Logout persistence
- Upload avatar, then log out from the app.
- Log back in with the same credentials.
- Verify the avatar still appears; if the app uses a server‑side store, confirm the image URL matches the upload.
Throughout the manual pass, keep a notebook or spreadsheet to record each test ID, the device model, Android version, and any deviations. This log becomes valuable when reproducing issues later.
Automated Testing – Strategies and Tooling
Manual testing is indispensable for exploratory work, but regression safety relies on automated checks that run on every commit. Android offers several layers for testing avatar upload, from unit‑level validation of image‑processing logic to end‑to‑end UI tests that exercise the full flow. Below we detail practical approaches, complete with code snippets you can drop into a typical Android Studio project.
1. Unit Tests for Image Preparation
Isolate the bitmap transformation and MIME‑type detection logic. Use Robolectric or plain JVM tests (if you avoid Android SDK calls) to verify:
- Down‑sampling respects a target max dimension (e.g., 1024 px).
- Exif orientation is applied correctly.
- HEIC files are converted to JPEG/PNG.
// AvatarImageProcessorTest.kt
@RunWith(RobolectricTestRunner::class)
class AvatarImageProcessorTest {
private val processor = AvatarImageProcessor()
@Test
fun `downsamples large bitmap`() {
val largeBitmap = BitmapFactory.decodeResource(
ApplicationProvider.getApplicationContext(),
R.drawable.test_20mp
)
val result = processor.prepareForUpload(largeBitmap, maxDimension = 1024)
assertTrue(result.width <= 1024 && result.height <= 1024)
}
@Test
fun `applies exif rotation`() {
val uri = Uri.parse("file:///sdcard/test_rotated.jpg")
val input = ApplicationProvider.getApplicationContext()
.contentResolver
.openInputStream(uri)!!
val original = BitmapFactory.decodeStream(input)
val processed = processor.applyExifOrientation(original, uri)
// Assuming the test image has a 90° clockwise rotation tag
assertEquals(original.height, processed.width)
assertEquals(original.width, processed.height)
}
}
These tests run fast on the JVM and give confidence that the core image pipeline won’t cause OOM or corruption.
2. Integration Tests with Espresso
Espresso excels at verifying UI interactions within the same process. Use it to test the picker launch, permission handling, and basic success/failure UI feedback. Because Espresso cannot directly interact with the system picker, we replace it with a mock chooser using Android’s Intent interception via IntentsTestRule.
// AvatarUploadEspressoTest.kt
@RunWith(AndroidJUnit4::class)
class AvatarUploadEspressoTest {
@get:Rule
val intentsRule = IntentsTestRule(ProfileActivity::class.java, true, false)
@Test
fun gallerySelection_showsSuccessToast() {
// Grant runtime permission before launching activity
grantPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
// Launch activity
intentsRule.launchActivity(Intent())
// Click avatar
onView(withId(R.id.avatar_image)).perform(click())
// Verify system picker intent is fired
intending(toPackage("com.android.documentsui"))
.respondWith(
Instrumentation.ActivityResult(Activity.RESULT_OK,
Intent().setData(Uri.parse("content://media/external/images/123")))
)
// Confirm crop (if any) – here we assume no crop UI
onView(withId(R.id.button_crop_ok)).perform(click())
// Verify toast
onView(withText(R.string.avatar_updated))
.inRoot(IsPlatformToast())
.check(matches(isDisplayed()))
}
@Test
fun permissionDenied_showsRationale() {
revokePermission(Manifest.permission.READ_EXTERNAL_STORAGE)
intentsRule.launchActivity(Intent())
onView(withId(R.id.avatar_image)).perform(click())
onView(withText(R.string.permission_storage_rationale))
.inRoot(withDecorView(not(is(intentsRule.activity.window.decorView))))
.check(matches(isDisplayed()))
}
}
Notes
- Use
grantPermission/revokePermissionhelper methods from AndroidX Test core (core:core-ktx). - The
IntentsTestRulelets you stub the chooser’s resultIntent. - For the camera path, substitute
MediaStore.ACTION_IMAGE_CAPTUREand return aBitmapextra.
3. End‑to‑End UI Tests with UI Automator
UI Automator can interact with system dialogs (permission picker, recent apps) and works across app boundaries, making it suitable for testing the actual system picker and permission flows. Below is a script that validates the happy path using UI Automator 2.x.
// AvatarUploadUiAutomatorTest.java
@RunWith(AndroidJUnit4.class)
public class AvatarUploadUiAutomatorTest {
private static final String PACKAGE = "com.example.myapp";
private UiDevice device;
@Before
public void setUp() {
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.pressHome();
}
@Test
public void testGalleryUpload_success() throws UiObjectNotFoundException {
// Clear app data to start fresh
device.executeShellCommand("pm clear " + PACKAGE);
// Launch app
Intent intent = new Intent();
intent.setPackage(PACKAGE);
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
Context context = InstrumentationRegistry.getTargetContext();
context.startActivity(intent);
// Wait for profile screen
device.wait(Until.hasObject(By.desc("Profile")), 5000);
// Click avatar
UiObject avatar = device.findObject(new UiSelector()
.resourceId(PACKAGE + ":id/avatar_image"));
avatar.clickAndWaitForNewWindow();
// Choose Gallery from chooser
UiObject galleryOption = device.findObject(new UiSelector()
.textContains("Gallery"));
galleryOption.clickAndWaitForNewWindow();
// In system picker, select first image
UiObject firstImage = device.findObject(new UiSelector()
.className("android.widget.ImageView")
.instance(0));
firstImage.clickAndWaitForNewWindow();
// Confirm crop (if present)
UiObject cropOk = device.findObject(new UiSelector()
.text("OK"));
if (cropOk.exists()) {
cropOk.click();
}
// Verify success toast
UiObject toast = device.findObject(new UiSelector()
.className("android.widget.Toast"));
assertTrue(toast.waitForExists(5000));
// Verify avatar updated (simple check: placeholder gone)
UiObject newAvatar = device.findObject(new UiSelector()
.resourceId(PACKAGE + ":id/avatar_image")
.checked(false));
assertTrue(newAvatar.waitForExists(5000));
}
}
Why UI Automator?
- It can press the system “Allow” button for runtime permissions.
- It works on API 21+ without requiring test-specific AndroidX dependencies.
- It enables testing of scenarios like rotating the device while the picker is open (you can send
device.setOrientationLeft()mid‑test).
4. Network Mocking with MockWebServer
To verify that the app correctly builds the multipart request and handles various server responses, use OkHttp’s MockWebServer in an instrumented test. This lets you assert on request headers, body parts, and response codes without hitting a real backend.
// AvatarUploadNetworkTest.kt
@RunWith(AndroidJUnit4::class)
class AvatarUploadNetworkTest {
private lateinit var mockWebServer: MockWebServer
private lateinit var avatarRepo: AvatarRepository
@Before
fun setUp() {
mockWebServer = MockWebServer()
mockWebServer.start()
val okHttpClient = OkHttpClient.Builder()
.url(mockWebServer.url("/api/avatar"))
.build()
avatarRepo = AvatarRepository(okHttpClient)
}
@After
fun tearDown() = mockWebServer.shutdown()
@Test
fun `uploads image and returns 200`() {
// Enqueue a successful response
mockWebServer.enqueue(MockResponse()
.setResponseCode(200)
.setBody("{\"url\":\"https://cdn.example.com/avatars/abc123\"}"))
// Create a tiny JPEG in memory
val bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565)
val outputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
val imageBytes = outputStream.toByteArray()
// Call repository
val result = avatarRepo.uploadAvatar(imageBytes)
assertTrue(result.isSuccess)
val recordedRequest = mockWebServer.takeRequest()
assertEquals("multipart/form-data", recordedRequest.getHeader("Content-Type"))
// Verify part name and filename
assertTrue(recordedRequest.getBody().readUtf8()
.contains("Content-Disposition: form-data; name=\"avatar\"; filename=\"image.jpg\""))
}
@Test
fun `handles 413 payload too large`() {
mockWebServer.enqueue(MockResponse()
.setResponseCode(413)
.setBody("File too large"))
val bitmap = Bitmap.createBitmap(2000, 2000, Bitmap.Config.RGB_565)
val output = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, output)
val result = avatarRepo.uploadAvatar(output.toByteArray())
assertFalse(result.isSuccess)
assertEquals(Result.Error::class.java, result::class.java)
}
}
These tests guarantee that the networking layer behaves correctly under success, client error, and server error conditions.
5. Using Firebase Test Lab for Device Matrix
Running Espresso/UI Automator tests on a single emulator does not capture device‑specific quirks (different OEM camera apps, varied storage providers, manufacturer‑specific permission dialogs). Firebase Test Lab lets you execute your instrumented test suite across a matrix of physical devices and Android versions. A typical gcloud command looks like:
gcloud firebase test android run \
--type instrumentation \
--app app-debug.apk \
--test app-debug-test.apk \
--device model=Pixel3,version=33,locale=en,orientation=portrait \
--device model=SamsungGalaxyS21,version=31,locale=en,orientation=landscape \
--timeout 90s
You can shard the test suite to run multiple tests in parallel, drastically reducing feedback time. Incorporate this step into your CI pipeline (GitHub Actions, Bitrise, etc.) so that every PR is validated on a representative set of devices.
Edge Cases That Only Appear in Production
Even the most thorough test matrix can miss issues that surface only under real‑world usage patterns. Below are several production‑only observations that have caused severe bugs in avatar uploads, along with suggestions on Android developers should watch for.
- Intermittent storage provider changes – Some file managers (e.g., Solid Explorer, CX File Explorer) return a
content://Uri that points to aDocumentFilebacked by a cloud service (Google Drive, Dropbox). TheContentResolver.openInputStreammay return a filtered stream that does not supportmark/reset, causing the down‑sampling code to fail silently. Mitigation: always copy the stream to a temporary file in cache before decoding.
- Background throttling on Android 12+ – If the upload is started from a foreground service but the app quickly moves to the background, the system may restrict network access, leading to apparent “stalls”. Using
WorkManagerwithsetExpedited(true)(for Android 12) or a foreground service with a persistent notification avoids this.
- Battery‑optimization whitelist – On certain Xiaomi or Oppo devices, aggressive battery saver kills background threads, causing the upload to be aborted after a few seconds. Adding a
android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONScheck and guiding users to whitelist the app can reduce reports.
- Locale‑dependent MIME detection – Some OEM gallery apps return MIME types with uppercase extensions (
.JPG) while the server expects lowercase. Normalizing MIME strings (mimeType.toLowerCase(Locale.US)) prevents 415 Unsupported Media Type errors.
- Network quality fluctuations – Users on 2G or fluctuating Wi‑Fi may experience partial uploads where the server receives a truncated multipart body. Implementing retry with exponential backoff and validating the
Content-Lengthheader before accepting the upload mitigates silent corruption.
- Multipart boundary collisions – When constructing the multipart body manually (instead of using a library like OkHttp), a poorly chosen boundary string can appear inside the image data, causing the server to misparse the request. Always rely on a well‑tested HTTP client that generates random boundaries.
- Concurrent uploads from multiple accounts – If the app supports account switching and does not clear the previous avatar’s Uri, a rapid account change can cause the wrong image to be uploaded. Ensure that each upload operation uses a freshly scoped
Uriand clears any stale references after completion.
- Accessibility service interference – Certain accessibility services (e.g., screen readers, switch control) inject synthetic clicks that can trigger the avatar button twice in rapid succession, bypassing debounce logic. Test with TalkBack and Switch Access enabled to confirm that your debounce or mutex guards against duplicate picker launches.
By incorporating these observations into your test matrix (e.g., adding a test case for “content Uri from cloud storage” or “battery optimization kill”), you reduce the gap between lab and production.
Quick‑Reference Checklist
Copy this list into your test plan or wiki. Tick each item as you verify it for a given release.
- [ ] Happy‑path gallery and camera flows produce a toast and update the UI instantly.
- [ ] Runtime permission denials show a clear rationale and do not crash.
- [ ] Large images (>12 MP) are down‑sampled without
OutOfMemoryError. - [ ] Zero‑byte or corrupted files are rejected with a user‑friendly message.
- [ ] Unsupported formats (HEIC, WEBP with alpha) either convert or show an explicit error.
- [ ] Network loss triggers retry or error; no indefinite spinner.
- [ ] Server error responses (413, 415, 500) map to appropriate UI messages.
- [ ] TalkBack announces success/failure states.
- [ ] Touch targets meet 48 dp minimum; color contrast passes WCAG AA.
- [ ] No raw image resides outside app‑private directories after upload.
- [ ] EXIF GPS and other metadata are stripped before transmission.
- [ ] Rapid double taps are debounced; only one picker opens.
- [ ] Device rotation mid‑picker does not crash or lose state.
- [ ] Avatar persists across logout/login sessions.
- [ ] Cache is cleared or overwritten on logout.
- [ ] Works on at least three representative device/OEM combos (Pixel, Samsung, Xiaomi).
- [ ] Tested with TalkBack, Switch Access, and battery‑optimization enabled.
If any item fails, mark the associated test ID from the matrix and prioritize a fix before release.
Closing Takeaways
Avatar upload may look like a trivial UI widget, but it is a convergence point for permissions, media handling, networking, concurrency, accessibility, and privacy. A disciplined testing strategy combines:
- A detailed matrix that enumerates happy paths, error paths, edge cases, accessibility, and security checks.
- Manual exploratory steps that catch device‑specific quirks, permission dialogs, and real‑world usability issues.
- Automated layers—unit tests for image processing, Espresso for UI logic, UI Automator for system dialogs, MockWebServer for network contracts, and Firebase Test Lab for broad device compatibility.
- Production‑aware observations such as cloud storage URIs, battery‑optimization kills, and locale‑specific MIME strings that only emerge under actual user load.
By treating avatar upload as a first‑class feature with its own test plan, you reduce the likelihood of embarrassing crashes, GDPR‑relevant data leaks, and poor user experiences that tarnish the very identity the avatar is meant to represent.
When you have the budget and desire to go beyond scripted checks, consider an autonomous, persona‑driven explorer like the one offered by SUSATest. It can roam the app with varied user profiles (curious, impatient, elderly, etc.) and surface scenarios—like a power user rapidly switching accounts while the picker is open—that static test scripts rarely anticipate. Combining such exploratory power with the structured matrix and automation described above gives you confidence that avatar upload works not just in the lab, but in the hands of every real user who taps that little circle to show their face.
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free