How to Test Cookie Consent on Android (Complete Guide)

Testing cookie consent on Android is not a niche activity; it is a core part of delivering a compliant, trustworthy app. When a user opens an application that loads web content—whether through a WebVi

June 08, 2026 · 18 min read · How-To Guides

Introduction

Testing cookie consent on Android is not a niche activity; it is a core part of delivering a compliant, trustworthy app. When a user opens an application that loads web content—whether through a WebView, Chrome Custom Tab, or a hybrid framework—the app must present a clear mechanism for accepting, rejecting, or managing cookies before any tracking scripts run. Failure to do so can lead to regulatory fines, loss of‑fines under GDPR, CCPA, or ePrivacy directives, erode user confidence, and break analytics pipelines that depend on consent‑gated data.

This guide walks you through why consent matters, how Android apps typically implement consent banners, a comprehensive test matrix covering happy paths, error conditions, accessibility, and security, step‑by‑step manual procedures, automated strategies with Espresso, UI Automator, Appium, and a look at autonomous persona‑driven exploration. Each section contains concrete commands, code snippets, and tables you can copy into your test repository. By the end you will have a repeatable checklist that you can bookmark and apply to any Android release.

---

Why Cookie Consent Matters on Android

Legal foundations

The General Data Protection Regulation (GDPR) requires a freely given, specific, informed, and unambiguous indication of the user’s wishes before personal data—including cookies that identify a device—can be processed. The ePrivacy Directive (often called the “cookie law”) adds that consent must be obtained before storing or accessing information on a user’s terminal equipment. In the United States, the California Consumer Privacy Act (CCPA) grants consumers the right to opt‑out of the sale of personal information, which includes many tracking cookies.

Business impact

Non‑compliance can trigger fines of up to 4 % of global annual turnover under GDPR, and CCPA penalties can reach $7 500 per intentional violation. Beyond regulator action, users are increasingly aware of privacy cues; a study by the Pew Research Center found that 79 % of smartphone users abandon an app when they feel their data is being harvested without clear consent.

Technical risk

If a consent banner fails to block analytics or ad SDKs, those libraries may fire network requests before the user has made a choice. This can contaminate data sets, skew A/B test results, and expose the app to data leakage bugs that are hard to reproduce in a staging environment where network throttling or consent mocks are not applied.

---

How Android Apps Present Cookie Consent

WebView‑based banners

Many apps embed a WebView that loads a consent management platform (CMP) hosted by a third party (OneTrust, TrustArc, Cookiebot, etc.). The WebView renders HTML, CSS, and JavaScript exactly as a browser would, and the app typically injects a JavaScript interface to read the user’s choice and store it in SharedPreferences or a SQLite database.

Chrome Custom Tabs

When an app launches a Custom Tab to display a privacy policy or consent page, the banner lives inside the Chrome process. Communication back to the host app is handled via CustomTabCallback or by reading a URL fragment that contains the consent token after the user taps “Accept”.

Native dialogs mimicking web banners

Some teams build fully native consent screens using Android XML layouts. These dialogs appear as an AlertDialog, a bottom sheet, or a full‑screen activity. Although they avoid WebView complexity, they still need to communicate the decision to any embedded web views or analytics SDKs.

Third‑party SDKs with built‑in consent handling

SDKs such as Google AdMob, Facebook Audience Network, or Firebase Analytics expose APIs like ConsentInformation.updateConsentStatus(...). The app must call these APIs after the user interacts with the banner; otherwise the SDK may initialize with a default state that assumes consent.

---

Test Matrix for Cookie Consent on Android

The following table enumerates test cases that cover functional, accessibility, localization, and security dimensions. Each row can be copied into a test‑management tool (Zephyr, Xray, TestRail) and linked to automated scripts.

Test IDCategoryDescriptionPreconditionsStepsExpected ResultPass/Fail Criteria
CC‑001Happy PathBanner appears on first launch and accepts consentFresh app install, no stored consent1. Launch app 2. Observe banner 3. Tap “Accept”Banner disappears, consent flag set to true, analytics SDK initializesPASS if banner dismissed and SharedPreferences.getBoolean("consent_given", false) returns true
CC‑002Happy PathBanner appears and rejects consentFresh app install1. Launch app 2. Observe banner 3. Tap “Reject”Banner disappears, consent flag set to false, analytics SDK does not send payloadsPASS if banner dismissed and no network request to analytics endpoint observed
CC‑003Error PathBanner fails to load due to missing CMP scriptDevice offline, CMP URL blocked by firewall1. Disable Wi‑Fi/mobile data 2. Launch app 3. Wait 10 sApp shows fallback message or proceeds without banner, no crashPASS if app does not throw uncaught exception and logs a warning
CC‑004Error PathBanner HTML contains malformed JavaScript causing WebView crashCMP serves broken JS1. Enable network 2. Point CMP to a test server returning 3. Launch appWebView stays alive, app shows error toast, consent not storedPASS if app does not crash and error is captured
CC‑005LocalizationBanner text respects device localeDevice set to French (fr‑FR)1. Set locale to French 2. Clear app data 3. Launch appBanner displays French translation, buttons labeled “Accepter” / “Refuser”PASS if all visible strings match fr‑FR resource file
CC‑006LocalizationRight‑to‑left layout for ArabicDevice set to Arabic (ar‑SA)1. Set locale to Arabic 2. Clear app data 3. Launch appBanner mirrors horizontally, text aligned rightPASS if layout direction is RTL and no clipping
CC‑007OrientationBanner survives orientation changeBanner visible1. Launch app 2. Rotate device to landscape 3. Rotate back to portraitBanner remains visible, consent state unchangedPASS if banner is present in both orientations and no flicker
CC‑008OrientationBanner reloads incorrectly on rotationBanner uses android:configChanges="orientation" incorrectlySame as CC‑007 but observe networkNo duplicate network request to fetch bannerPASS if only one request occurs
CC‑009Low MemorySystem kills WebView while banner is showingDevice with 2 GB RAM, background memory‑heavy app1. Start memory‑stress app 2. Launch target app 3. Wait for bannerApp recovers gracefully, shows fallback or retains consent if already setPASS if no NullPointerException from WebView and app stays responsive
CC‑010Network LatencyBanner delayed due to slow CMP responseNetwork throttled to 50 kbps1. Apply throttle via tc or Charles 2. Launch app 3. Wait up to 30 sBanner eventually appears, user can interactPASS if banner appears within 30 s and no timeout crash
CC‑011Consent RevocationUser can withdraw consent via settingsConsent previously given1. Launch app 2. Open Settings → Privacy → Manage Consent 3. Tap “Withdraw” 4. Restart appBanner reappears on next launch, consent flag falsePASS if banner shown again and analytics disabled
CC‑012Multiple LayersFirst‑party banner and third‑party SDK banner both presentApp uses its own banner and loads an ad SDK that also shows consent1. Launch app 2. Observe both bannersOnly the topmost banner is interactive; underlying SDK respects the decisionPASS if lower layer does not fire tracking calls before user choice
CC‑013Accessibility – TalkBackBanner reachable via TalkBackTalkBack enabled1. Enable TalkBack 2. Launch app 3. Swipe to focusFocus lands on banner title, then each button, with correct labelsPASS if all elements are focusable and announce “Accept button, double tap to accept”
CC‑014Accessibility – Font ScaleBanner respects 200 % font scalingDevice font size set to Largest1. Set font size to Largest 2. Launch appBanner text scales, layout does not truncate buttonsPASS if all text visible and buttons tappable
CC‑015Accessibility – ContrastBanner meets WCAG AA contrastUse a color analyzer1. Capture screenshot 2. Run contrast checkText/background ratio ≥ 4.5:1PASS if ratio meets threshold
CC‑016Security – Cookie AttributesCookies set after consent have Secure and SameSite flagsConsent given, network sniffing enabled1. Accept consent 2. Capture HTTP responses with mitmproxy 3. Inspect Set‑Cookie headersEach cookie includes Secure and SameSite=Strict or SameSite=LaxPASS if all tracking cookies satisfy both attributes
CC‑017Security – No Sensitive Data in Cookie ValueCookie does not contain email, token, PIISame as CC‑0161. Accept consent 2. Examine cookie valueValue is random identifier or emptyPASS if no recognizable PII appears
CC‑018Security – Consent Revocation Clears CookiesWithdrawing consent removes or invalidates cookiesConsent given, cookies present1. Accept consent 2. Capture cookies 3. Withdraw consent via settings 4. Capture cookies againNo tracking cookies present or they are marked as expiredPASS if cookie jar is empty or all tracking cookies have Expires in the past
CC‑019Inter‑App InteractionLaunching Custom Tab from another app respects consentAnother app uses CustomTabIntent to open your consent page1. Set consent to false 2. From external app launch https://example.com/consent via Custom Tab 3. Observe behaviorConsent banner shown, user must act before proceedingPASS if banner appears and no tracking occurs before interaction
CC‑020Ad Network ConsentAd SDK does not initialize before consentApp integrates AdMob1. Launch app with consent false 2. Wait for ad requestNo ad request sent to googleads.g.doubleclick.netPASS if ad network stays idle until consent given

*Notes:*

---

Manual Testing Approach

Setting up the environment

  1. Device or emulator – Use a Pixel 4 API 33 emulator for baseline tests and a physical device with Android 13 for real‑world validation.
  2. Clear app stateadb uninstall com.example.app && adb install app-release.apk ensures a clean install.
  3. Network control – Install *Charles Proxy* or *mitmproxy* on your host machine, then point the device to the proxy via Wi‑Fi settings (hostname of host, port 8888). Enable SSL proxying to inspect HTTPS traffic.
  4. Accessibility tools – Turn on TalkBack (Settings → Accessibility → TalkBack) and Switch Access if you need to validate alternative navigation.
  5. Log capture – Run adb logcat -v threadtime > logcat.txt before launching the app; filter later with grep Consent.

Step‑by‑step manual checklist

StepActionObservationTool
1Install a fresh buildNo prior consent storedadb install
2Launch the appConsent banner appears within 2 sVisual inspection
3Verify banner text matches localeCorrect language, proper RTL if neededScreenshot + diff
4Enable TalkBackFocus moves to banner title, then each buttonTalkBack feedback
5Change font size to LargestText scales, buttons remain tappableVisual
6Toggle dark modeBanner colors adapt, contrast ≥ 4.5:1System settings
7Rotate deviceBanner persists, no duplicate network calladb logcat + proxy
8Simulate low memoryRun a memory‑hog (adb shell am broadcast -a com.example.MEMORY_HOG)Observe no crash
9Apply network throttlingBanner delayed but eventually loadsCharles throttle settings
10Tap “Accept”Banner disappears, consent flag trueadb shell settings get global consent_given or inspect SharedPreferences
11Verify analytics endpoint not called before acceptanceNo request to analytics host in proxymitmproxy filter
12Tap “Reject”Banner disappears, consent flag false, analytics idleSame as 10/11
13Withdraw consent via settingsBanner reappears on next launchSettings UI
14Check cookie attributesEach Set‑Cookie includes Secure and appropriate SameSitemitmproxy → Response → Set‑Cookie
15Inspect cookie value for PIINo email, token, or personal dataManual scan
16Close app, reopenConsent state persisted across sessionsSharedPreferences check
17Uninstall and reinstallFresh start shows banner againadb uninstall + install

Observing UI with TalkBack

When TalkBack is on, swipe right to move focus. The first spoken element should be the banner’s heading (e.g., “We use cookies to improve your experience”). Subsequent swipes should announce each action button with its label and state (“Accept button, not selected”). If any element is skipped or announces incorrectly, note the ID and fix the contentDescription or labelFor attribute.

Logging network traffic with Charles / mitmproxy

Configure the proxy to intercept *.doubleclick.net, *.google-analytics.com, and any custom endpoints. After each user action, filter the timeline for Set‑Cookie headers and POST/ GET requests. The presence of a request before consent indicates a failure in the consent‑gating logic.

Checking SharedPreferences / SQLite for stored consent

Most CMPs write a boolean flag:


adb shell run-as com.example.app cat shared_prefs/com.example.app_prefs.xml

Look for . If the flag is missing or incorrect after a user interaction, the app’s consent storage layer needs review.

---

Automated Testing Approaches

Espresso UI tests

Espresso works well for native consent screens and for interacting with WebView content when you enable WebView testing. Below is a concise test that validates the happy‑path accept flow.


@RunWith(AndroidJUnit4.class)
public class ConsentBannerTest {

    @Rule
    public ActivityTestRule<MainActivity> activityRule =
            new ActivityTestRule<>(MainActivity.class, true, false);

    @Test
    public void acceptConsent_hidesBanner_andSetsFlag() {
        // Launch with a clean state
        activityRule.launchActivity(new Intent());

        // Wait for banner to appear (assuming id consent_banner)
        onView(withId(R.id.consent_banner))
                .check(matches(isDisplayed()));

        // Tap Accept
        onView(withId(R.id.btn_accept))
                .perform(click());

        // Banner should be gone
        onView(withId(R.id.consent_banner))
                .check(matches(not(isDisplayed())));

        // Verify SharedPreference
        Context context = InstrumentationRegistry.getInstrumentation()
                .getTargetContext();
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        assertTrue(prefs.getBoolean("consent_given", false));
    }
}

Key points

UI Automator for cross‑app scenarios

When the consent flow launches a Chrome Custom Tab, UI Automator can interact with the Chrome UI:


@Test
public void customTabConsent_shownBeforeNavigation() {
    UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
    // Assume MainActivity has a button that opens the privacy page
    onView(withId(R.id.open_privacy)).perform(click());

    // Wait for Custom Tab to appear
    UiObject2 tab = device.wait(Until.findObject(By.clazz("androidx.customview.widget.ViewPager")),
            5000);
    assertNotNull(tab);

    // Check for consent banner inside the tab (WebView)
    UiObject2 banner = device.findObject(By.text("We use cookies"));
    assertTrue(banner.isDisplayed());

    // Accept within the tab
    UiObject2 acceptBtn = device.findObject(By.id("accept-button"));
    acceptBtn.click();

    // Verify that the target URL loads after consent
    UiObject2 loaded = device.wait(Until.findObject(By.text("Privacy Policy")),
            8000);
    assertNotNull(loaded);
}

Appium for hybrid/WebView testing

Appium lets you drive the native container and then switch to the WebView context to manipulate the consent banner. The following Python snippet demonstrates a full flow on an emulator or real device.


from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

caps = {
    "platformName": "Android",
    "deviceName": "Pixel_4_API33",
    "appPackage": "com.example.app",
    "appActivity": ".MainActivity",
    "automationName": "UiAutomator2",
    "noReset": True
}

driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
wait = WebDriverWait(driver, 15)

# 1. Wait for native banner container
banner = wait.until(EC.presence_of_element_located((MobileBy.ID, "consent_banner")))
assert banner.is_displayed()

# 2. Switch to WebView context (assuming the banner lives inside a WebView)
webview_context = [ctx for ctx in driver.contexts if "WEBVIEW" in ctx][0]
driver.switch_to.context(webview_context)

# 3. Click the Accept button inside the WebView
accept_btn = wait.until(EC.element_to_be_clickable((MobileBy.ID, "accept-button")))
accept_btn.click()

# 4. Return to native context
driver.switch_to.context(driver.contexts[0])

# 5. Verify banner disappeared
native_banner = wait.until(EC.invisibility_of_element_located((MobileBy.ID, "consent_banner")))
assert not native_banner.is_displayed()

driver.quit()

Tips

SUSATest autonomous exploration

SUSA can be invoked from the command line after installing the agent:


pip install susatest-agent
susatest-agent run \
    --apk path/to/app-release.apk \
    --personas curious impatient novice adversarial elderly accessibility power_user \
    --output ./susatest-report.json \
    --timeout 300

The agent explores the app without any pre‑written scripts. Each persona drives a distinct interaction style:

During the run, SUSA records:

Because SUSA does not rely on deterministic locators, it can discover issues such as a banner that only appears after a specific sequence of navigation (e.g., opening the side drawer, then tapping “Settings”, then returning to the home screen) – a path that a scripted test would never consider unless explicitly added.

---

Edge Cases That Only Appear in Production

CategorySituationWhy it hides in scripted testsDetection tip
Locale‑specific textBanner uses a hard‑coded English string for “Accept” in a French localeScripts often set locale once at test start and never change it mid‑runRun a matrix of locales and verify string resources
Dark mode renderingBanner background set to #FFFFFF causing low contrast in dark modeEmulators may start in light mode; tests forget to toggle uiModeUse adb shell cmd uimode night yes/no before launch
Orientation‑triggered reloadBanner fetches fresh HTML on each rotation due to missing android:configChangesAutomated tests frequently lock orientation to portraitRotate device programmatically and watch network logs
Low‑memory killerSystem kills the WebView process while the banner is visible, leaving a blank screenMost test devices have ample memory; CI runners rarely simulate pressureUse adb shell am send-trim-memory com.example.app MODERATE
Network latencyConsent request takes >10 s; app shows a spinner that never dismisses because timeout logic is missingTests run on localhost or fast LAN, never throttlingApply tc qdisc add dev eth0 root netem delay 8000ms
Consent revocation after updateNew app version clears old SharedPreferences key, causing banner to reappear despite prior acceptanceUpdate scenarios are rarely exercised in CISimulate an upgrade: install v1, give consent, install v2 over it, launch
Multiple consent layersFirst‑party banner + third‑party ad SDK banner both present; SDK ignores the first‑party decisionScripts test only the app’s own bannerIntegrate a mock ad SDK that logs when it initializes
Banner overlapping system UIOn devices with a notch or gesture bar, the banner appears under the cut‑out, making buttons untappableScreenshots taken on emulator without notchTest on a range of device profiles (Pixel 4 XL, Samsung Galaxy S22, etc.)
Accessibility override (font scale 200 %)Layout uses fixed dp heights causing text cutoffMany UI tests rely on default font sizeSet settings put system font_scale 2.0 and rerun
Insecure SharedPreferences storageConsent flag stored in MODE_WORLD_READABLE (deprecated but still present in some legacy code)Automated checks rarely inspect file permissionsUse adb shell run-as com.example.app ls -l /data/data/com.example.app/shared_prefs/
SameSite=None without SecureBackend sends Set‑Cookie: sessionid=abc123; SameSite=None missing Secure flagCookie inspection is often omitted from UI testsCapture Set‑Cookie headers and assert presence of Secure
XSS via cookie valueMalicious cookie contains that gets injected into a WebView via document.cookieTests rarely inject malicious values; they assume sane dataUse mitmproxy to rewrite Set‑Cookie with a script payload and verify it does not execute

These edge conditions are precisely where autonomous, persona‑driven exploration shines: a curious user may repeatedly open the navigation drawer, an impatient user may spam the back button while a banner is loading, and an accessibility user with enlarged fonts may trigger layout overflows that a scripted test never reaches.

---

Accessibility Testing Deep Dive

TalkBack navigation order

  1. Open TalkBack (Settings → Accessibility → TalkBack).
  2. Launch the app.
  3. Swipe right repeatedly; the focus should move from the status bar (if visible) to the banner title, then to each action button, then to any underlying content.
  4. If focus jumps or skips an element, inspect the view’s android:importantForAccessibility attribute; set it to yes for banner children.

Switch Access

Font scaling and layout breaks

Color contrast verification

Screen reader announcements for consent actions

---

Security and Privacy Checks

Inspecting cookie attributes via network logs

Using mitmproxy, add a script to print Set‑Cookie lines:


def response(flow):
    for k, v in flow.response.headers.items_all():
        if k.lower() == "set-cookie":
            print(f"[SET-COOKIE] {v}")

Run the proxy, perform the consent flow, and verify each line contains Secure and either SameSite=Strict or SameSite=Lax.

Verifying SameSite and Secure flags

A quick Bash one‑liner to audit a mitmproxy dump file (dump.txt):


grep -i "set-cookie" dump.txt | while read line; do
    if [[ ! "$line" =~ Secure ]] || [[ ! "$line" =~ SameSite=(Strict|Lax) ]]; then
        echo "NON‑COMPLIANT: $line"
    fi
done

Any output indicates a cookie that fails the baseline security policy.

Ensuring no sensitive data in cookie value

Extract cookie values and run a regex scan for patterns that look like email addresses, tokens, or personal identifiers:


grep -oP 'sessionid=\K[^;]+' dump.txt | grep -Ei '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}'

If the command returns matches, the app is inadvertently storing PII in a cookie.

Testing consent revocation clears cookies

  1. Accept consent → capture cookie jar (mitmproxy --set save_stream_file=cookies_before).
  2. Revoke consent via settings → capture again (cookies_after).
  3. Diff the two files: any tracking cookie present in after signals a failure.

Checking for third‑party tracking without consent

Configure mitmproxy to block all domains except those explicitly whitelisted (your own API and the CMP). Run the app; any request that gets blocked indicates a tracker that fired before consent.

---

Checklist for Cookie Consent Testing on Android

✅ ItemDescription
Functional

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