How to Test Terms Acceptance on Android (Complete Guide)
Terms acceptance screens are the gatekeepers between a user’s first interaction and the core functionality of an app. If the flow is broken, users cannot proceed, leading to abandoned installs, negati
Why Terms Acceptance Testing Matters on Android
Terms acceptance screens are the gatekeepers between a user’s first interaction and the core functionality of an app. If the flow is broken, users cannot proceed, leading to abandoned installs, negative reviews, and potential compliance violations. On Android, the acceptance UI often appears as a full‑screen dialog, a scrollable web view, or a custom native controls that must be tapped, scrolled, and sometimes typed into (e.g., entering a birthdate to confirm age). A single missed tap‑target, an unhandled orientation change, or a dialog that fails to dismiss can block the entire user stranded.
From a business perspective, the acceptance step is frequently tied to legal obligations (GDPR, COPPA, regional telecom regulations). A failure to record consent correctly can expose the company to fines or injunctions. From a QA perspective, the screen is a high‑risk area because it combines multiple UI patterns: long‑form text, checkboxes, radio buttons, scrollbars, and sometimes embedded links that launch external browsers. Testing it thoroughly uncovers bugs that would otherwise surface only after a release, when support tickets start piling up.
Core Concepts: What Constitutes a Terms Acceptance UI
Before designing tests, clarify the exact elements you need to validate. Most Android implementations share a common skeleton, though styling varies.
| Element | Typical Android Implementation | Validation Points |
|---|---|---|
| Title bar | Toolbar or TextView with “Terms of Service” | Visible, correct spelling, accessible label |
| Scrollable content | NestedScrollView containing a TextView (HTML‑styled) or WebView | Full text reachable, no truncated paragraphs, correct font scaling |
| Acceptance control | CheckBox, Switch, RadioButton, or custom Button that says “I Agree” | Initially unchecked, togglable, state persisted |
| Decline control | Often a “Cancel” or “Do Not Accept” button | Exists, leads to app exit or fallback flow |
| Links | ClickableSpan or WebView intercepting URLs | Opens correct URL, handles target=_blank, returns to acceptance screen |
| Confirmation dialog | AlertDialog after tapping accept | Shows summary, provides final confirm/cancel |
| Persistence flag | SharedPreference, DB entry, or backend flag | Written only after final confirm, readable on app restart |
Understanding these pieces lets you map each test case to a concrete UI interaction rather than a vague “check the terms”.
Test Matrix: Scenarios to Cover
A comprehensive matrix separates happy‑path verification from error, edge, accessibility, and security concerns. The table below lists each scenario, the expected outcome, and the Android‑specific artefacts to inspect.
| ID | Category | Description | Steps | Expected Result | Android‑Specific Checks |
|---|---|---|---|---|---|
| T1 | Happy Path | User reads, accepts, proceeds | 1. Launch app → terms screen appears 2. Scroll to bottom 3. Tap checkbox 4. Tap “Accept” 5. Confirm in dialog | App moves to main screen, consent flag set | Verify SharedPreference key terms_accepted=true; ensure no residual UI fragments |
| T2 | Happy Path – Decline | User declines and exits | Same as T1 but tap “Decline” | App closes or shows fallback (e.g., login) | Confirm finish() called; no consent flag written |
| T3 | Error – Missing Checkbox | Checkbox not interactable due to overlay | Launch app, note overlay (ad banner) covering checkbox | User cannot tap checkbox; should be warned or overlay removed | Use UI Automator to detect overlapping view bounds; log overlap >0dp |
| T4 | Error – Scroll Lock | Scrollview disabled, preventing reach of bottom checkbox | Rotate device, attempt scroll | Checkbox remains out of reach; acceptance blocked | Verify isScrollEnabled() true after rotation; ensure layout params not fillViewport=false |
| T5 | Edge – Long Text Performance | Terms exceed 10 000 words, causing UI lag | Launch app, measure time to scroll to bottom with Systrace | Scroll smooth (<16ms per frame) | Check for GlobalLayoutListener causing repeated layout passes |
| T6 | Edge – Orientation Change Mid‑Scroll | User rotates while scrolling | Scroll halfway, rotate to landscape, continue scroll | Scroll position preserved, no UI jump | Confirm onSaveInstanceState retains scroll offset; NestedScrollView restores state |
| T7 | Edge – Font‑Size Scaling | User sets system font size to 200 % | Launch app, verify text readability | All text fully visible, no clipping, checkboxes still tappable | Test with Configuration.fontScale >= 2.0; ensure sp units used |
| T8 | Accessibility – TalkBack | User navigates with TalkBack | Enable TalkBack, swipe to each element | Each element announces correctly, checkbox state announced | Verify contentDescription on checkbox, accessibilityLiveRegion on scrollable area |
| T9 | Accessibility – Color Contrast | Low contrast text on background | Use accessibility scanner | Contrast ratio ≥4.5:1 for normal text | Run axe-android or Accessibility Test Framework; flag failures |
| T10 | Security – Clickjacking | Malicious overlay attempts to hijack accept button | Simulate overlay service covering accept button with transparent layer | Accept button not clickable; overlay detected and blocked | Use WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE detection; log overlay permission usage |
| T11 | Privacy – Data Leakage | Terms text transmitted to analytics before acceptance | Enable network proxy, launch app | No request containing terms content sent before consent | Inspect HTTP(S) logs; ensure no POST/PUT with terms body pre‑accept |
| T12 | Regression – Consent Persistence After Update | App updated, consent flag must survive | Set consent flag, install new APK version, launch | App skips terms screen, proceeds directly | Compare SharedPreferences values pre/post install; ensure no wipe |
| T13 | Regression – Locale Change | User switches language after accepting | Accept in English, change system language to Spanish, restart | Terms screen not shown again (consent stored) | Verify that flag is locale‑independent; check that UI strings update only on new consent |
| T14 | Edge – Network Delay Loading WebView | Terms hosted remotely, slow connection | Throttle network to 50 kbps, launch app | Loading spinner shown, timeout handled gracefully, fallback to cached copy | Use WebViewChromeClient.onProgressChanged; ensure WebView does not block UI thread |
| T15 | Error – Dialog Dismissal Outside | User taps outside confirmation dialog | Show accept dialog, tap outside area | Dialog should not dismiss unless setCancelable(true) | Confirm dialog.setCanceledOnTouchOutside(false) in code; test both settings |
The matrix above gives you a concrete backlog. Each row can be turned into a test case, a manual exploratory session, or an automated script.
Manual Testing Approach: Step‑by‑Step
Even when automation is in place, a manual pass catches nuances that scripts ignore (e.g., visual glitches, unexpected gestures). Follow this procedure on a physical device or emulator with API level 21+.
- Setup
- Install the app under test via
adb install -r app.apk. - Clear data:
adb shell pm clear com.example.app. - Grant any needed permissions (e.g.,
android.permission.SYSTEM_ALERT_WINDOWfor overlay tests).
- Baseline Happy Path
- Launch the app. Verify the terms screen appears immediately (no splash screen interference).
- Use TalkBack to navigate to the scrollable region; swipe down until you hear “end of list”.
- Tap the acceptance checkbox; confirm it toggles with a tactile click.
- Tap the primary action button (usually “Accept”).
- Observe the confirmation dialog; verify the text matches the selected terms version.
- Tap “Confirm”. The app should transition to the main dashboard.
- Open Settings → Apps → [Your App] → Storage → Clear cache (do not clear data). Relaunch the app; ensure the terms screen does not reappear.
- Decline Path
- Repeat steps 1‑2 but tap the “Decline” button instead of accepting.
- Confirm the app either exits (
adb shell pkill com.example.app) or shows a login/welcome screen. - Verify no consent flag is written (
adb shell run-as com.example.app cat shared_prefs/prefs.xml | grep terms_accepted).
- Error Injection
- Overlay Test: Start a transparent overlay service that covers the lower 20 % of the screen (
adb shell service call activity 42 s16 com.example.overlay). Attempt to accept; note that the checkbox is unresponsive. Log the touch event withadb shell getevent. - Scroll Lock: Manually set
android:isScrollEnabled="false"in the layout viaadb shell sed -i 's/isScrollEnabled="true"/isScrollEnabled="false"/' /data/data/com.example.app/shared_prefs/...(requires root). Try to scroll; confirm the checkbox stays out of reach. - Network Throttle: Use
adb shell tc qdisc add dev wlan0 root netem delay 500msto simulate latency; launch app and watch for a timeout or missing content.
- Accessibility Checks
- Enable TalkBack (
adb shell settings put secure accessibility_enabled 1). - Swipe through each element; ensure each announces its role, state, and value.
- Disable TalkBack, enable Font Size 200 % in Settings → Accessibility → Font size. Verify no clipping.
- Run the built‑in Accessibility Scanner (
adb shell am start -c android.intent.category.LAUNCHER -a android.intent.action.MAIN -n com.google.android.apps.accessibility.audit/.AuditActivity) and review the report for contrast failures.
- Security / Privacy
- Install
httptoolkitorCharles Proxyon your workstation, configure the device to use it as a Wi‑Fi proxy. - Launch the app, watch the proxy for any outbound requests containing the terms text before you tap accept.
- For clickjacking, enable “Draw over other apps” for a test app that draws a transparent view over the accept button; attempt to tap and confirm the tap does not register.
- Post‑Test Cleanup
- Clear app data again (
adb shell pm clear). - Disable any overlay services (
adb shell pm disable-user --user 0 com.example.overlay). - Reset network settings (
adb shell tc qdisc del dev wlan0 root).
Manual testing is time‑consuming but invaluable for spotting visual regressions, gesture conflicts, and platform‑specific quirks that automated checks may miss if they rely solely on view hierarchy assertions.
Automated Testing on Android: Tools and Frameworks
Automation accelerates regression and lets you run the matrix on every CI build. Below are the most relevant Android‑specific tools, their strengths, and where they fall short for terms acceptance testing.
| Tool | Language | Best For | Limitations for Terms Acceptance |
|---|---|---|---|
| Espresso | Java/Kotlin | Fast, deterministic UI tests within the same process | Requires test source in the app; cannot test external WebView content without JavaScript injection |
| UI Automator | Java/Kotlin | Cross‑app interactions, system dialogs, rotation | Slower than Espresso; limited to API 21+; fragile with custom views |
| Appium | Java, JavaScript, Python, etc. | Black‑box testing, works on real devices & emulators, supports WebView contexts | Server overhead; slower startup; flaky with complex animations |
| SUSA Autonomous Agent | CLI (Python) | No‑script exploration, persona‑driven, regression script generation | Still maturing for highly customized native views; best as a supplement |
| Firebase Test Lab | Cloud (any) | Running Espresso/UI Automator/Instrumentation tests on many device configurations | Cost; limited to test suites you upload; no built‑in persona modeling |
Choosing the Right Approach
- Use Espresso for pure‑native acceptance screens (checkboxes, buttons,
NestedScrollView). - Use UI Automator when you need to verify system dialogs (e.g., “Draw over other apps” permission prompt) or test orientation changes that temporarily detach the activity from the test process.
- Use Appium if your terms are displayed inside a
WebViewand you need to inspect DOM or run JavaScript to verify text completeness. - Use SUSA for exploratory runs that surface edge cases you never thought to script (e.g., a rare gesture that opens a hidden settings pane).
Sample CI Pipeline Snippet
# .gitlab-ci.yml
stages:
- build
- test
build:
stage: build
script:
- ./gradlew assembleDebug assembleAndroidTest -Dorg.gradle.jvmargs="-Xmx4g -XX:MaxMetaspaceSize=512m"
instrumentedTest:
stage: test
script:
- ./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.numShards=2 -Pandroid.testInstrumentationRunnerArguments.shardIndex=0
- ./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.numShards=2 -Pandroid.testInstrumentationRunnerArguments.shardIndex=1
artifacts:
when: always
reports:
junit: **/TEST-*.xml
paths:
- **/outputs/**
The above splits instrumentation tests across two shards to parallelize execution on a farm of devices.
Code Examples: Espresso Test for Terms Acceptance
Below is a self‑contained Espresso test that validates the happy path, the decline path, and a simple accessibility assertion. Place it in src/androidTest/java/com/example/app/TermsAcceptanceTest.kt.
package com.example.app
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.*
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TermsAcceptanceTest {
private lateinit var context: Context
@Before
fun setUp() {
context = InstrumentationRegistry.getInstrumentation().targetContext
// Ensure clean state before each test
context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
.edit()
.clear()
.apply()
}
@After
fun tearDown() {
// Optional: verify no stray preferences left
val prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
assertFalse("Consent flag should be cleared after test", prefs.getBoolean("terms_accepted", false))
}
@Test
fun happyPath_acceptAndProceed() {
// 1. Verify terms screen is shown
onView(withId(R.id.terms_title))
.check(matches(isDisplayed()))
.check(matches(withText(R.string.terms_title)))
// 2. Scroll to bottom of the NestedScrollView
onView(withId(R.id.terms_scroll))
.perform(swipeDown()) // repeat until end; Espresso will keep trying
// More robust: use a custom action that scrolls until a view is visible
onView(withId(R.id.accept_checkbox))
.perform(scrollTo(), click())
// 3. Press the primary accept button
onView(withId(R.id.btn_accept))
.perform(click())
// 4. Confirmation dialog appears
onView(withText(R.string.dialog_confirm_terms))
.check(matches(isDisplayed()))
// 5. Press final confirm
onView(withId(android.R.id.button1)).perform(click())
// 6. App navigates to main screen
onView(withId(R.id.main_content))
.check(matches(isDisplayed()))
// 7. Persisted flag
val prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
assertTrue("Terms acceptance flag not stored", prefs.getBoolean("terms_accepted", false))
}
@Test
fun declinePath_exitsApp() {
onView(withId(R.id.btn_decline)).perform(click())
// Assuming the app calls finish() on decline; we verify the activity is no longer in foreground
val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val runningTasks = am.getAppTasks()
assertTrue("App should be backgrounded after decline", runningTasks.isEmpty() || runningTasks[0].getActivityInfo().packageName != context.packageName)
}
@Test
fun accessibility_checkboxAnnouncesState() {
// Enable TalkBack via instrumentation (requires API 23+)
val ui = InstrumentationRegistry.getInstrumentation().uiAutomation
ui.executeShellCommand("settings put secure accessibility_enabled 1")
onView(withId(R.id.accept_checkbox))
.check(matches(isDisplayed()))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
// TalkBack will announce state; we can't directly assert spoken text, but we can ensure contentDescription is set
.check(matches(withContentDescription(containsString("Checkbox"))))
}
}
Explanation of key points
scrollTo()combined withclick()ensures the checkbox is brought into view before interaction, handling dynamic list lengths.- The test clears
SharedPreferencesin@Beforeto guarantee a clean slate. - The decline test uses
ActivityManagerto confirm the app is backgrounded; adjust according to your app’s actual exit strategy (some apps show a fallback screen instead of finishing). - The accessibility test merely checks that a
contentDescriptionexists; real TalkBack validation would require a manual pass or a tool likeAndroid Accessibility Test Framework.
Code Examples: Appium Test for WebView‑Based Terms
If your terms are rendered inside a WebView, you need to switch contexts to interact with DOM elements. The following Python script demonstrates a full flow using Appium 2.0.
# terms_acceptance_appium.py
from appium import webdriver
from appium.options.android import UiAutomator2Options
from appium.webdriver.common.mobileby import MobileBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
def get_driver():
options = UiAutomator2Options()
options.set_platform_name("Android")
options.set_automation_name("UiAutomator2")
options.set_app_package("com.example.app")
options.set_app_activity(".MainActivity")
options.set_no_reset(True) # keep existing consent flag between runs if desired
return webdriver.Remote("http://localhost:4723/wd/hub", options=options)
def wait_for_context(driver, context_name, timeout=10):
end = time.time() + timeout
while time.time() < end:
if context_name in driver.contexts:
return context_name
time.sleep(0.5)
raise TimeoutException(f"Context {context_name} not found")
def test_terms_acceptance():
driver = get_driver()
try:
# Wait for the native terms screen to appear
WebDriverWait(driver, 15).until(
EC.presence_of_element_located((MobileBy.ID, "terms_title"))
)
# Scroll to bottom of WebView (assuming it fills the screen)
webview_context = wait_for_context(driver, "WEBVIEW_com.example.app")
driver.switch_to.context(webview_context)
# Inside WebView: locate the scrollable container and scroll
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(1) # allow lazy‑loading of any dynamic sections
# Locate the checkbox (could be a <input type="checkbox">)
checkbox = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((MobileBy.XPATH, "//input[@type='checkbox']"))
)
if not checkbox.is_selected():
checkbox.click()
# Switch back to native to click the Accept button
driver.switch_to.context("NATIVE_APP")
accept_btn = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((MobileBy.ID, "btn_accept"))
)
accept_btn.click()
# Handle confirmation dialog (native AlertDialog)
confirm_btn = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((MobileBy.ID, android.R.id.button1))
)
confirm_btn.click()
# Verify navigation to main screen
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((MobileBy.ID, "main_content"))
)
# Optional: verify consent flag via ADB shell
# driver.execute_script("mobile: shell", {"command": "run-as com.example.app cat shared_prefs/prefs.xml"})
finally:
driver.quit()
if __name__ == "__main__":
test_terms_acceptance()
Key takeaways
- Context switching is essential; the script waits for the
WEBVIEW_context before attempting to locate DOM elements. - A short
sleepafter scrolling gives the WebView time to render any lazy‑loaded sections (common with long terms hosted on a CDN). - The test reverts to
NATIVE_APPto interact with the Android buttons that sit outside the WebView. - For CI, you can run this script inside a Docker container with Appium server and an Android emulator or a real device farm.
Autonomous Persona‑Driven Exploration: How It Finds Hidden Bugs
Scripted tests follow predetermined paths; they cannot anticipate unusual user behaviours such as rapid tapping, accidental gestures, or unconventional navigation orders. An autonomous explorer like SUSA simulates distinct personas (curious, impatient, novice, adversarial, elderly, accessibility‑focused, power user) each with its own probability distribution over actions (e.g., a power user may long‑press to open context menus, an impatient user may spam the back button).
When SUSA encounters a terms acceptance screen, it:
- Models the screen – builds a graph of interactable elements (checkboxes, buttons, links, scrollable containers).
- Applies persona‑driven policies – e.g., the “elderly” persona uses larger tap targets, slower swipe velocity, and prefers explicit buttons over gestures.
- Generates sequences – each run produces a unique flow (scroll → long‑press on a link → back → rapid tap on checkbox → rotate → etc.).
- Checks oracles – after each action‑acles** – watches for crashes, ANRs, unhandled exceptions, accessibility violations (via integrated axe), and consent‑flag correctness.
Because the explorer does not rely on hardcoded assertions, it can surface bugs such as:
- A link inside the terms that opens a
Chrome Custom Tabwhich, when closed, leaves the acceptance dialog in a half‑dismissed state (only visible when the user rapidly taps back after the tab closes). - An impatient user double‑tapping the accept button causing a race condition where the consent flag is written twice, leading to a backend duplicate‑record error.
- An accessibility‑focused user who enables font scaling to 300 % and discovers that the scrollable container’s height is calculated in
px, causing the bottom checkbox to be rendered off‑screen on certain tablet densities.
Susa also retains knowledge of dead ends (e.g., a swipe that leads nowhere) and avoids repeating them in later runs, making each session more efficient. The platform then auto‑generates regression scripts (Espresso for Android, Playwright for Web) that capture the exact interaction sequence that revealed the bug, turning exploratory findings into repeatable CI checks.
To run SUSA locally:
# Install the agent
pip install susatest-agent
# Point it at an APK or a Play Store URL
susatest explore --app ./app-debug.apk --personas elderly,impatient,accessibility --output ./susa-report.json
The resulting JSON contains a timeline of events, screenshots, and any detected violations. You can feed the susa-report.json into your CI as an additional quality gate.
Production‑Only Edge Cases: What Scripts Miss
Even the most exhaustive test matrix can overlook issues that only manifest under real‑world conditions:
| Phenomenon | Why It Evades Lab Tests | Detection Strategy |
|---|---|---|
| Battery‑Optimizer Killing Background Services | On some OEM skins, aggressive doze modes terminate the app’s JobScheduler that writes consent to a remote server after acceptance, causing the server to think the user never agreed. | Use adb shell cmd jobscheduler list before and after acceptance; monitor for job completion. Pair with a server‑side webhook that logs receipt of consent. |
| Carrier‑Specific SMS OTP Interference | In regions where the app sends an OTP via SMS immediately after acceptance, certain carriers delay SMS delivery, making the user think the flow failed and they retry, creating duplicate consent entries. | Simulate carrier delay with adb shell emulator -netdelay gprs and monitor the backend for duplicate entries. |
| Multi‑Window / Split‑Screen Mode | When the app runs in split‑screen, the terms WebView may receive touch events intended for the adjacent app, leading to mis‑taps. | Launch the app in split‑screen via adb shell am start -S com.example.app/.TermsActivity com.android.chrome/.Main and verify that touch coordinates are correctly mapped. |
| Dynamic Feature Module Download | If the terms screen resides in a dynamic feature module that is downloaded on‑first‑use, a flaky network can cause the module to install partially, showing a stale version of the terms. | Use adb shell pm set-install-location 2 to force install to external storage, then toggle airplane mode mid‑download and observe UI. |
| Accessibility Service Interference | A third‑party accessibility service (e.g., a screen‑reader overlay) can inject click events that bypass the consent checkbox, recording acceptance without user interaction. | Install a test accessibility service that logs AccessibilityEvent.TYPE_VIEW_CLICKED; verify that clicks on the checkbox originate from the user touch event (event.getSource() vs. event.getSource().getClassName()). |
| Locale‑Specific Text Expansion | Languages like German can expand UI strings by up to 30 %, causing the accept button to overlap with the scrollbar, making it untouchable on low‑resolution screens. | Run the app with adb shell setprop persist.sys.locale de-DE and use UI Automator to verify that the accept button’s bounds do not intersect the scrollbar’s bounds. |
| Dark Mode Contrast Regression | Some apps define separate color resources for night mode; a mistake can leave the checkbox indicator with insufficient contrast against the dark background, violating WCAG AA. | Force night mode via adb shell ui mode night yes and run an accessibility scanner; flag any contrast ratio < 4.5:1. |
These scenarios typically require a combination of system‑level commands, backend observability, and real‑device variability—elements that are hard to encode in a pure unit or instrumentation test but are well‑suited to an autonomous, persona‑driven agent that can inject system states (network throttling, locale changes, multi‑window) as part of its exploration.
Checklist: Terms Acceptance Testing Quick Reference
Print or keep this list handy for release‑candidate verification. Each item maps to a row in the earlier matrix.
| ✅ | Item | How to Verify |
|---|---|---|
| 1 | Terms screen launches on first start | Clear app data, launch, confirm presence of terms_title |
| 2 | Full text is scrollable without truncation | Scroll to bottom, ensure last paragraph is visible |
| 3 | Acceptance control starts unchecked | Verify checkbox state via isChecked() |
| 4 | Tapping the control toggles state | Click, assert opposite state |
| 5 | Accept button disabled until checkbox checked | Espresso: withId(R.id.btn_accept).check(matches(not(isEnabled()))) then after check, isEnabled() |
| 6 | Confirmation dialog shows correct summary | Verify dialog text matches selected terms version |
| 7 | Final confirm navigates to main screen | Assert main‑screen UI element visible |
| 8 | Decline path leads to expected fallback (exit/login) | Press decline, check for finish() or fallback UI |
| 9 | Consent flag persisted after accept | SharedPreferences contains terms_accepted=true |
| 10 | Consent flag absent after decline | Flag missing or false |
| 11 | No network leak of terms prior to accept | Proxy capture shows no POST/PUT with terms body |
| 12 | Overlay cannot hijack accept button | Transparent overlay service present → touch events not received on accept |
| 13 | Scroll works after orientation change | Rotate while scrolling, confirm position retained |
| 14 | Font scaling up to 200 % does not clip text | Set fontScale=2.0, verify all lines fully visible |
| 15 | TalkBack reads each element correctly | Enable TalkBack, swipe, listen for appropriate announcements |
| 16 | Color contrast ≥ 4.5:1 in light and dark modes | Run accessibility scanner in both UI modes |
| 17 | No crash or ANR when rapid‑tapping accept | Monkey test (adb shell monkey -p com.example.app -c android.intent.category.LAUNCHER 500) focused on accept button |
| 18 | Split‑screen does not misroute touches | Launch in split‑screen, tap accept, confirm action |
| 19 | Dynamic feature module installs fully before showing terms | Simulate flaky network, verify module version matches expected |
| 20 | Third‑party accessibility service cannot falsify consent | Install test service, confirm its clicks are not counted as user consent |
Mark any item that fails; investigate the root cause using the logs, screenshots, or SUSA report generated during exploration.
Closing Takeaways
Testing terms acceptance is not a ceremonial checkbox; it is a linchpin for legal compliance, user trust, and conversion. On Android, the interaction surface mixes native UI, scrollable containers, WebViews, and system dialogs, creating a combinatorial space where bugs hide.
A solid strategy blends three layers:
- Manual exploratory passes that catch visual glitches, gesture conflicts, and OEM‑specific quirks.
- Deterministic automation (Espresso/UI Automator/Appium) for regression of happy‑path, error‑path, accessibility, and basic security checks.
- Autonomous persona‑driven exploration (e.g., SUSA) that surfaces edge cases only real users trigger—rapid taps, font‑size extremes, multi‑window, locale shifts, and third‑party service interference.
When you incorporate the matrix, the checklist, and the tooling snippets above into your CI/CD pipeline, you shift from hoping the terms screen works to *knowing* it works under the conditions your users actually encounter. The result is fewer support tickets, lower regulatory risk, and a smoother first‑time experience for every user who opens your app.
---
*Keep this guide bookmarked. When you receive a new build, run the matrix, let SUSA roam for a few cycles, and watch the regression suite turn green. Your users—and your legal team—will thank you.*
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