How to Test Reports Generation on Android (Complete Guide)
Reports are often the final deliverable that users see after completing a workflow such as expense tracking, medical data aggregation, or financial reconciliation. When a report is missing, malformed,
Why Testing Reports Generation Matters on Android
Reports are often the final deliverable that users see after completing a workflow such as expense tracking, medical data aggregation, or financial reconciliation. When a report is missing, malformed, or contains incorrect data, user trust erodes quickly and regulatory compliance can be jeopardized. On Android, reports frequently involve file I/O, external storage permissions, third‑party libraries (PDF, CSV, Excel), and sharing intents that traverse process boundaries. A defect in any of these layers can surface only under specific device states—low storage, locale change, or background restriction—making exhaustive testing essential.
Common Failure Modes in Reports Generation
Understanding the typical ways reports break helps focus test effort. The most frequent failure categories observed in production are:
| Failure Category | Typical Symptom | Root Cause |
|---|---|---|
| Data truncation | Report ends mid‑row or missing fields | Buffer size miscalculation or premature stream close |
| Formatting errors | Misaligned columns, wrong date locale, garbled Unicode | Incorrect use of SimpleDateFormat, hard‑coded separators |
| Permission denial | FileNotFoundException when writing to external storage | Missing WRITE_EXTERNAL_STORAGE runtime request or scoped storage mis‑handling |
| Storage exhaustion | Zero‑byte file or IOException: No space left | Not checking available space before writing large payloads |
| Intent resolution failure | Share dialog does not appear or opens wrong app | Incorrect MIME type or missing Intent.FLAG_GRANT_READ_URI_PERMISSION |
| Accessibility breakage | TalkBack skips report preview or announces garbled text | Non‑semantic views, missing content descriptions, low contrast |
| Security leak | Report written to world‑readable directory or logged in plaintext | Insecure file path, logging of sensitive data |
Each of these categories can be reproduced with a targeted test matrix, which we define next.
Building a Comprehensive Test Matrix
A test matrix captures the combinations of input conditions, device states, and expected outcomes that must be verified. Below is a master table that can be copied into a test‑management tool (e.g., TestRail, Zephyr). Each row represents a distinct test case; you can add or remove columns based on your project’s needs.
| TC‑ID | Scenario | Precondition | Steps | Expected Result | Notes |
|---|---|---|---|---|---|
| RPT‑01 | Happy path PDF generation | User logged in, data set >0 rows, external storage granted | 1. Navigate to Reports screen 2. Tap “Generate PDF” 3. Wait for toast “Report saved” | PDF file created in Documents/AppName/reports/ with correct name, size >0, and valid PDF header (%PDF-) | Verify with PdfiumAndroid or File API |
| RPT‑02 | Happy path CSV export | Same as RPT‑01 | 1. Choose CSV format 2. Tap Export 3. Confirm save location | CSV file with correct delimiter (, or locale‑specific), UTF‑8 BOM if required, all rows present | Open with spreadsheet app to validate |
| RPT‑03 | Error: storage full | Device storage <10 MB free | 1. Fill storage with large files via ADB (adb shell dd if=/dev/zero of=/sdcard/bigfile bs=1M count=500) 2. Attempt report generation | Generation fails gracefully, shows error dialog “Insufficient storage”, no partial file left | Ensure cleanup of test files after |
| RPT‑04 | Error: permission denied (Android 13+) | App targeting API 33, no MANAGE_EXTERNAL_STORAGE granted, scoped storage enabled | 1. Deny storage permission at runtime 2. Try to save to legacy path /sdcard/Download/ | Generation fails, error message “Permission required”, fallback to app‑specific directory (getExternalFilesDir) | Test both denial and grant paths |
| RPT‑05 | Error: interrupted write | Simulate kill during I/O | 1. Start generation 2. Immediately run adb shell am kill 3. Relaunch app | No corrupted file; either file absent or contains valid partial data that app can detect and discard | Use File.deleteOnExit() or check file size consistency |
| RPT‑06 | Edge case: locale change to RTL | Device language set to Arabic (right‑to‑left) | 1. Set locale via Settings → Language → Arabic 2. Generate report | Report layout mirrors correctly, numbers not reversed, date format follows Arabic locale | Verify with screenshot comparison |
| RPT‑07 | Edge case: font scaling 200% | Developer options → Font size → Largest | 1. Increase font size 2. Open report preview | All text readable, no clipping, layout adapts (use ConstraintLayout or ScrollView) | Important for accessibility compliance |
| RPT‑08 | Edge case: dark mode | System theme set to Dark | 1. Enable dark mode 2. Generate report | Report uses appropriate color contrast (WCAG AA minimum 4.5:1) for text vs background | Check with accessibility scanner |
| RPT‑09 | Security: world‑readable file | App writes to /sdcard/Download/report.pdf without MODE_PRIVATE | 1. Generate report 2. Run adb shell ls -l /sdcard/Download/report.pdf | File permissions are -rw------- (owner only) or app‑specific directory | Prevent data leakage |
| RPT‑10 | Privacy: log leakage | Report contains PII (e.g., email) | 1. Enable logcat filter for package 2. Generate report 3. Observe logs | No PII appears in logcat output | Use ProGuard rules to strip logging or Timber with level checks |
| RPT‑11 | Sharing intent: correct MIME | PDF report generated | 1. Tap Share button 2. Choose email app | Intent action ACTION_SEND, type application/pdf, URI granted with FLAG_GRANT_READ_URI_PERMISSION | Verify receiving app can open file |
| RPT‑12 | Background restriction | Battery optimization enabled for app | 1. Put app in background during generation 2. Wait 30 s | Generation completes or is paused/resumed correctly; no ANR | Use JobScheduler or WorkManager to survive background limits |
| RPT‑13 | Multi‑window mode | Device in split‑screen with another app | 1. Generate report while other app occupies top half 2. Interact with both apps | Report generation UI remains responsive, no layout overlap issues | Test with Android Studio emulator multi‑window |
| RPT‑14 | Interrupted network (if report fetches data) | Disable Wi‑Fi/mid‑download | 1. Start report that pulls data from server 2. Toggle airplane mode 3. Observe behavior | Generation shows retry or offline fallback, does not crash | Depends on architecture; include if applicable |
| RPT‑15 | Concurrent report generation | User taps generate twice quickly | 1. Tap Generate 2. Immediately tap again before first finishes | Only one generation runs; second request either queues or shows “already generating” | Prevents file corruption or duplicate files |
*Tip:* When you copy this matrix into a test‑case tool, add columns for Automation Status (Manual, Automated, Planned) and Owner to track progress.
Sub‑tables for Specific Domains
For brevity in daily stand‑ups you may extract focused tables:
Accessibility Sub‑matrix
| TC‑ID | Condition | Steps | Expected |
|---|---|---|---|
| RPT‑06 | RTL locale | Set language to Arabic, generate report | Layout mirrors, no truncated text |
| RPT‑07 | Font scale 200% | Increase font size, preview report | All text visible, no overlap |
| RPT‑08 | Dark mode | Enable dark theme, generate report | Contrast ratio ≥4.5:1 |
| RPT‑?? | TalkBack navigation | Enable TalkBack, swipe through report preview | Each element announced correctly, no skipped nodes |
Security/Privacy Sub‑matrix
| TC‑ID | Condition | Steps | Expected |
|---|---|---|---|
| RPT‑09 | World‑readable file | Check file permissions after write | -rw------- |
| RPT‑10 | Log leakage | Filter logcat for package, generate report | No PII in logs |
| RPT‑?? | Intent URI permission | Share report, verify FLAG_GRANT_READ_URI_PERMISSION set | Receiving app can open file |
Manual Testing Approach: Step‑by‑Step Guide
Even with automation, a disciplined manual pass catches nuances that scripts may miss, especially around user perception and device‑state interactions. Follow this procedure for each new report feature or after a major refactor.
1. Environment Setup
- Devices: Use a matrix of physical devices covering API levels 21‑34, different manufacturers (Samsung, Pixel, OnePlus), and varying screen densities.
- Emulators: Include at least one API 33 emulator with Play Store to test Google‑Play‑services‑dependent features (e.g., Firebase Crashlytics).
- Tooling: Install Android Studio, ADB,
adb shell pm grantfor legacy tests, andandroid.permission.WRITE_EXTERNAL_STORAGE uiautomatorviewerfor UI inspection. - Data: Prepare a static JSON fixture set (small, medium, large) that mimics real‑world payloads. Store it in
src/androidTest/assets/for easy push viaadb push.
2. Preparing Test Data
- Load the fixture into the app via a debug menu or directly through the UI (e.g., “Load Sample Data”).
- Verify that the data appears correctly in the preview screen before report generation.
- For large‑data tests, use a generator script (Kotlin or Python) to create a 10 MB JSON file; push it to
/sdcard/Download/and load via file picker.
3. Executing Happy Path Tests
- Navigate to the report screen.
- Select the desired format (PDF, CSV, Excel).
- Tap Generate.
- Wait for the success toast or notification.
- Open the file via a file manager or the built‑in preview.
- Validate:
- File exists in expected directory.
- Size >0.
- Header/magic number matches format.
- Content matches source data (spot‑check a few rows).
- Document the outcome with a screenshot and the file’s SHA‑256 hash (
adb shell sha256sum).
4. Executing Error Path Tests
- Storage Full: Follow the precondition in RPT‑03, then attempt generation. Observe the error dialog; confirm no file is left behind.
- Permission Denial: Disable the permission via Settings → Apps →
→ Permissions → Storage → Deny. Attempt generation; verify the fallback path or error message. - Interrupted Write: Use
adb shell am killmid‑generation (you can monitor logs to know when the write starts). After relaunch, check that either no file exists or the file is valid and usable.
5. Logging and Evidence Collection
- Capture logcat with
adb logcat -v time > logcat.txtbefore each test series. - After each test, pull the generated report:
adb pull /sdcard/Documents/AppName/reports/report_20240926.pdf ./artifacts/. - Store artifacts in a folder named after the test‑case ID for traceability.
- If a failure occurs, annotate the logcat with timestamps and the exact UI state (screenshot from
adb shell screencap).
6. Post‑Test Cleanup
- Delete any test files created in shared storage to avoid interfering with subsequent runs.
- Reset device locale, font size, and theme to default values.
- Revoke any temporarily granted permissions (
adb shell pm revoke).android.permission.WRITE_EXTERNAL_STORAGE
Automated Testing on Android: Tools and Frameworks
Manual validation is essential but does not scale for regression. Automated checks provide fast feedback on CI pipelines and guard against re‑introducing known defects. Below we detail the layers of automation that work best for report generation on Android.
Unit Testing Report Generation Logic
If the report creation is encapsulated in a pure Kotlin/Java class (e.g., ReportGenerator), unit tests can verify data transformation without UI or file system involvement.
// ReportGeneratorTest.kt
class ReportGeneratorTest {
private val generator = ReportGenerator()
@Test
fun `pdf generator writes correct header`() {
val data = listOf(ReportRow("Alice", 28, "alice@example.com"))
val output = generator.generatePdf(data) // returns ByteArray
assertTrue(output.startsWith(byteArrayOf(0x25, 0x50, 0x44, 0x46))) // "%PDF"
}
@Test
fun `csv generator respects locale delimiter`() {
val data = listOf(ReportRow("Bob", 35, "bob@example.com"))
val csv = generator.generateCsv(data, Locale.GERMANY)
assertTrue(csv.contains(";")) // German locale uses semicolon
}
}
Run these tests with ./gradlew testAndroidUnit. They execute on the JVM, providing sub‑second feedback.
Instrumented UI Tests with Espresso
Espresso validates that the UI triggers the generation flow and that the success state is observable.
// ReportGenerationTest.kt
@RunWith(AndroidJUnit4::class)
class ReportGenerationTest {
@get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class)
@Test
fun generatePdf_showsSuccessToast_andCreatesFile() {
// Load sample data via debug menu
onView(withId(R.id.btnLoadSample)).perform(click())
onView(withId(R.id.btnGeneratePdf)).perform(click())
// Wait for toast
onView(withText(containsString("Report saved")))
.inRoot(IsPlatformToast())
.check(matches(isDisplayed()))
// Verify file exists using a helper that runs on a background thread
val context = ApplicationProvider.getApplicationContext()
val file = File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS),
"reports/report_${System.currentTimeMillis()}.pdf")
assertTrue("File should exist", file.exists())
// Optional: check PDF header
val header = FileInputStream(file).readNBytes(4)
assertArrayEquals(byteArrayOf(0x25, 0x50, 0x44, 0x46), header)
}
}
Key points:
- Use
IdlingResourceif the generation performs asynchronous work (e.g., coroutines). - Leverage
FileProviderto expose the generated file for assertions without needingREAD_EXTERNAL_STORAGEin the test manifest (grant temporary permission viagrantUriPermission).
UIAutomator for File System Checks
When you need to inspect the file system outside the app’s sandbox (e.g., verify that a report appears in the public Downloads folder), UIAutomator works well because it can run with broader permissions.
// ReportFileCheck.java
public class ReportFileCheck extends UiAutomatorTestCase {
public void testPdfAppearsInDownloads() throws Exception {
// Launch the app and trigger generation
final String pkg = getTargetContext().getPackageName();
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.launchActivity(new Intent(Intent.ACTION_MAIN)
.setPackage(pkg)
.setCategory(Intent.CATEGORY_LAUNCHER));
// Click generate button (replace with actual resource ID)
UiObject2 genBtn = device.findObject(By.res(pkg, "id/generatePdf"));
genBtn.click();
// Wait for toast
UiObject2 toast = device.findObject(By.text(contains("Report saved")));
assertNotNull(toast);
// Check file in Downloads
File downloads = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
File[] files = downloads.listFiles((dir, name) -> name.endsWith(".pdf"));
assertTrue("No PDF found in Downloads", files.length > 0);
// Validate first file’s header
FileInputStream fis = new FileInputStream(files[0]);
byte[] header = new byte[4];
fis.read(header);
assertArrayEquals(new byte[]{(byte)0x25, 0x50, 0x44, 0x46}, header);
fis.close();
}
}
Run with ./gradlew connectedAndroidTest. This test can be tagged (androidTestAnnotation) to run only on nightly builds if it is slower.
Leveraging SUSA Autonomous Exploration (Mention)
SUSA’s autonomous agent can be pointed at the APK or a internal test build. It will explore the reports flow using its built‑in personas (curious, impatient, novice, etc.) without any test scripts. During exploration it:
- Attempts to generate reports after every navigation change, uncovering race conditions that only appear when the user rapidly switches tabs.
- Triggers permission dialogs and observes how the app handles denial versus grant.
- Varies system settings (locale, font scale, dark mode) via its persona‑driven configuration matrix, surfacing UI layout bugs that Espresso may miss if the test script does not change those settings.
- Logs any crashes, ANRs, or file‑system exceptions and automatically creates a regression script in Appium (Android) and Playwright (Web) for future CI runs.
To invoke SUSA locally:
pip install susatest-agent
susatest run --apk path/to/app-debug.apk --goal "reports" --personas all --output ./susa-reports
The agent will produce a JSON summary with discovered issues, each tagged with the responsible persona (e.g., “impatient” found a double‑tap crash).
Integrating with CI/CD
- Unit tests: Run on every pull request (
./gradlew testAndroidUnit). - Instrumented tests: Execute on a device farm (Firebase Test Lab, AWS Device Farm) for each merge to main.
- Susa runs: Schedule a nightly job that pulls the latest
artifact.apkfrom the build pipeline, executes the agent with a curated set of personas, and fails the build if any new crash or ANR is detected. - Artifact archiving: Store generated reports and logs as build artifacts for audit purposes.
Code Snippets: Espresso Test for PDF Report Generation
Below is a self‑contained example that demonstrates how to verify PDF content using the PdfiumAndroid library, which can parse PDF pages and extract text for assertions.
// PdfReportVerificationTest.kt
@RunWith(AndroidJUnit4::class)
class PdfReportVerificationTest {
@get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class)
@Test
fun generatedPdfContainsExpectedData() {
// Preload known data
onView(withId(R.id.btnLoadFixture)).perform(click())
onView(withId(R.id.btnGeneratePdf)).perform(click())
// Wait for generation completion
onView(withText("Report saved"))
.inRoot(IsPlatformToast())
.check(matches(isDisplayed()))
// Locate the newest PDF in app‑specific docs
val context = ApplicationProvider.getApplicationContext()
val docsDir = File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "reports")
val pdfFile = docsDir.listFiles()
.filter { it.name.endsWith(".pdf") }
.maxByOrNull { it.lastModified() }
?: error("No PDF found")
// Load PDF with PdfiumAndroid
val document = PdfDocument.openFile(pdfFile.absolutePath)
val page = document.getPage(0)
val text = page.text
page.close()
document.close()
// Assert that known strings appear
assertTrue(text.contains("Alice Smith"))
assertTrue(text.contains("2024-09-26"))
// Optional: check that no extra pages were added
assertEquals(1, document.pagesCount)
}
}
Explanation
- The test loads a deterministic fixture so the expected output is known.
- After generation, it waits for the success toast (a reliable UI signal).
- It scans the app‑specific
reportsdirectory for the most‑recent PDF, avoiding race conditions with parallel test runs. PdfiumAndroidextracts the text from the first page; assertions confirm that the data from the fixture is present.- Resources are closed promptly to avoid file‑handle leaks.
Code Snippets: UIAutomator to Validate Report Sharing Intent
Sharing a report often involves an Intent with a content:// URI. This UIAutomator test confirms that the intent is correctly formed and that the receiving app can read the file.
// ShareIntentVerification.java
public class ShareIntentVerification extends UiAutomatorTestCase {
public void testShareIntentHasCorrectMimeAndUriPermission() throws Exception {
String pkg = getTargetContext().getPackageName();
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// Launch app and navigate to report screen
device.launchActivity(new Intent(Intent.ACTION_MAIN)
.setPackage(pkg)
.setCategory(Intent.CATEGORY_LAUNCHER));
device.wait(Until.hasObject(By.pkg(pkg).depth(0)), 5000);
// Load sample data (assuming a debug button)
device.findObject(By.res(pkg, "id/loadSample")).click();
device.wait(Until.hasObject(By.text("Data loaded")), 3000);
// Trigger share
device.findObject(By.res(pkg, "id/sharePdf")).click();
// Wait for the chooser to appear
UiObject2 chooser = device.wait(Until.hasObject(By.text("Share via")), 5000);
assertNotNull(chooser);
// Retrieve the intent sent to the chooser via instrumentation
Intent sentIntent = getInstrumentation().getTargetContext()
.getSystemService(Context.class)
.getSystemService(NotificationManager.class)
.getActiveNotifications()[0]
.notification
.contentIntent
.getIntent();
assertNotNull(sentIntent);
assertEquals(Intent.ACTION_SEND, sentIntent.getAction());
assertEquals("application/pdf", sentIntent.getType());
Uri uri = (Uri) sentIntent.getParcelableExtra(Intent.EXTRA_STREAM);
assertNotNull(uri);
// Verify that the URI grants read permission to the chooser
int flag = sentIntent.getFlags();
assertTrue((flag & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0);
// Optionally, launch a known viewer (e.g., Chrome) and confirm it opens
ComponentName chrome = new ComponentName("com.android.chrome", "com.google.android.apps.chrome.Main");
device.executeShellCommand("am start -n " + chrome.flattenToString() + " -d " + uri.toString());
device.wait(Until.hasObject(By.clazz("android.webkit.WebView")), 8000);
}
}
What this test does
- Launches the app, loads data, and triggers the share flow.
- Captures the intent that the system sends to the chooser.
- Validates action, MIME type, and the presence of the
FLAG_GRANT_READ_URI_PERMISSIONflag. - Optionally starts a viewer app to ensure the URI is readable.
Edge Cases that Only Appear in Production
Even with exhaustive matrices, certain conditions surface only after the app reaches a broad user base. Below are the most insidious ones, why they escape lab testing, and how to mitigate them.
| Edge Case | Why It’s Missed in Lab | Detection Strategy | Mitigation |
|---|---|---|---|
| Low‑storage kill during write | Emulators usually start with ample free space; CI agents often clean storage before each run. | Use adb shell shell pm set-install-location 2 to force install on SD, then fill storage with a script that leaves <5 MB free. Run generation in a loop. | Check StorageManager.getStorageStats() before writing; show warning and abort if free space < 2× expected file size. |
| Interrupted write due to process kill | Unit tests mock I/O; Espresso tests wait for UI completion, not actual kill. | Inject a CountDownLatch in the repository layer that, when triggered, calls android.os.Process.killProcess(Process.myPid()). Verify that no half‑written file remains. | Write to a temporary file in the app‑specific cache directory, then rename atomically (File.renameTo) after successful close. |
| Locale‑specific number formatting | Test devices often default to en_US; testers may not switch to ar_EG or th_TH. | Automate locale matrix via adb shell setprop persist.sys.language . | Use NumberFormat.getInstance(locale) and DateFormat.getDateInstance(DateFormat.DEFAULT, locale) everywhere; avoid hard‑coded patterns. |
| Font scaling breaking layout | Designers review on default font size; QA may not enable the largest size. | Run UI tests with adb shell settings put system font_scale 2.0 and assert that no view’s height exceeds parent constraints. | Use wrap_content with maxLines and ellipsize, or rely on ConstraintLayout chains that adjust. |
| Dark mode contrast failures | Dark mode toggles are often overlooked; contrast checks require specialized tools. | Integrate Android’s AccessibilityTestFragment (from the Accessibility Test Framework) into your instrumented suite to run on each build. | Define color resources with ?attr/colorOnBackground and verify contrast ≥4.5:1 using the framework’s API. |
| Multi‑window resize causing UI jitter | Most tests run in full‑screen mode; split‑screen is rarely exercised. | Use UIAutomator to drag the divider (adb shell cmd window resize --mainRatio 0.3) while the report generation is in progress. | Ensure that all UI elements use ConstraintLayout or LinearLayout with weight; avoid fixed dp dimensions that exceed smaller pane width. |
| Background restriction stopping WorkManager | Doze mode and App Standby can defer work; tests often disable battery optimizations locally. | Enable battery optimization for the app via adb shell cmd appops set , then trigger a work request and verify it completes after the maintenance window. | Use setExpedited(true) for urgent work, or provide a foreground service with a persistent notification when immediate completion is required. |
| Scoped storage permission regression on Android 13+ | Developers test on Android 12 where legacy storage still works; they forget to handle the new model. | On an API 33 device, deny MANAGE_EXTERNAL_STORAGE and attempt to write to Environment.DIRECTORY_DOWNLOADS. Verify fallback to getExternalFilesDir. | Always use MediaStore APIs for public‑facing files, or save to app‑specific directories and use ACTION_OPEN_DOCUMENT for user‑chosen locations. |
| Concurrent report generation causing file clashes | Manual testing rarely taps the generate button twice in quick succession. | Use a monkey runner script: adb shell monkey -p and monitor for duplicate file names or crashes. | Make the generation function synchronized or use a Mutex from Kotlin coroutines; generate file names with UUID or timestamp + random suffix. |
| Network loss mid‑report (if data is fetched) | Unit tests mock the network; UI tests often have constant connectivity. | Toggle airplane mode (adb shell cmd connectivity airplane-mode enable) after the request starts but before it completes. | Implement retry with exponential backoff, cache intermediate results, and show an offline placeholder. |
Production‑level monitoring
- Integrate Firebase Crashlytics to capture native and Java exceptions that occur during report generation.
- Add custom keys:
report_format,report_size_bytes,storage_free_mbto help triage. - Use Firebase Performance Monitoring to trace the duration of the generation process; set alerts if the 95th‑percentile exceeds a threshold (e.g., 8 s on median device).
Checklist for Reports Generation Testing
Copy this list into your team’s wiki or Definition of Done (DoD) for any report‑related feature.
- [ ] Happy path verified for each supported format (PDF, CSV, Excel, HTML).
- [ ] Error paths: storage full, permission denial, interrupted write, network loss (if applicable).
- [ ] Locale matrix: at least
en_US,fr_FR,ar_EG,ja_JP,zh_CN– verify numbers, dates, and currency symbols. - [ ] Accessibility: font scale 100%–200%, TalkBack navigation, dark mode contrast ≥4.5:1.
- [ ] Security: file written to app‑specific directory or scoped storage; no world‑readable permissions; no PII in logs or Crashlytics custom keys.
- [ ] Privacy: opt‑out respected; if user disables analytics, report generation does not send telemetry.
- [ ] Sharing intent: correct MIME,
FLAG_GRANT_READ_URI_PERMISSIONgranted, chooser appears, receiving app can open file. - [ ] Background behavior: generation completes or is paused/resumed correctly under Doze, battery optimization, and foreground service restrictions.
- [ ] Multi‑window / multi‑display: UI remains usable, no overlapping controls, report preview scales.
- [ ] Concurrency: double‑tap or rapid successive generates do not corrupt files or crash the app.
- [ ] Performance: 95th‑percentile generation time < X s on median device (define X based on product spec).
- [ ] Regression: automated unit + instrumented tests pass on every PR; SUSA autonomous run reports no new crashes/ANRs on nightly builds.
Closing
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