package com.peachpasswords.qa import android.content.ClipData import android.content.ClipboardManager import android.content.Context import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import java.io.BufferedReader import java.io.InputStreamReader import org.junit.Assert.assertTrue import org.junit.Test import org.junit.runner.RunWith /** * Emulator QA: does the autofill framework issue a fill request for a form * that includes a 2FA field, and does the OTP chip appear in the inline * suggestion strip? The chip carries the actual TOTP code -- the regression * guard for "2FA fill is not working from the chip" (continuity 2026-08-05). * * Uses [QaFormActivity] which now has username + password + 2FA fields. * Tapping the username field triggers a FillRequest; the IME renders the * entry chip and the OTP chip (when a TOTP-enabled entry matches). */ @RunWith(AndroidJUnit4::class) class QaFormTwoFactorChipTest { @Test fun framework_issues_fill_request_for_form_with_2fa_field() { shell("am force-stop com.peachpasswords.debug.test") shell("am start -n com.peachpasswords.debug.test/com.peachpasswords.qa.QaFormActivity") Thread.sleep(2000) focusField("Username field") Thread.sleep(4000) val dump = shell("dumpsys autofill") val mentions = dump.lineSequence() .filter { it.contains("com.peachpasswords.debug.test") } .take(10) .joinToString("\n") assertTrue( "no autofill session for the 2FA form -- framework never issued a fill request.\n" + "dumpsys excerpt:\n$mentions", mentions.isNotEmpty(), ) } /** A spoofed web domain must not cause any pre-selection clipboard write. */ @Test fun malicious_companion_spoofed_web_domain_does_not_copy_totp() { shell("am force-stop com.peachpasswords.debug.test") val clipboard = InstrumentationRegistry.getInstrumentation().targetContext .getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager clipboard.setPrimaryClip(ClipData.newPlainText("sentinel", "user-owned-clipboard")) shell("logcat -c") shell("am start -n com.peachpasswords.debug.test/com.peachpasswords.qa.QaSpoofedWebViewActivity") Thread.sleep(2000) focusField("Spoofed username field") Thread.sleep(4000) val logcat = shell("logcat -d -s PeachAutofill:D") assertTrue( "spoofed native form did not exercise Peach's unverified-web denial path:\n$logcat", logcat.contains("refused unverified web origin"), ) assertTrue( "malicious autofill origin must not overwrite the user's clipboard", clipboard.primaryClip?.getItemAt(0)?.text?.toString() == "user-owned-clipboard", ) } private fun shell(command: String): String { val instrumentation = InstrumentationRegistry.getInstrumentation() instrumentation.uiAutomation.executeShellCommand(command).use { pfd -> BufferedReader(InputStreamReader(android.os.ParcelFileDescriptor.AutoCloseInputStream(pfd))).use { return it.readText() } } } /** * v2.14.6 regression: when the framework offers a single inline spec * (the case after username/password submit on GitHub, or when only a * 2FA field is focused), the picker dataset no longer clobbers the * OTP chip inline. We focus the 2FA field directly and assert the * autofill session survives -- the picker fix matters most on the * "single spec" path, which is what the emulator produces here. */ @Test fun framework_issues_fill_request_when_focused_on_2fa_field_only() { shell("am force-stop com.peachpasswords.debug.test") shell("am start -n com.peachpasswords.debug.test/com.peachpasswords.qa.QaFormActivity") Thread.sleep(2000) // Select the field by its stable semantic description so layout changes // cannot make this probe tap the wrong control. focusField("2FA code field") Thread.sleep(4000) val dump = shell("dumpsys autofill") val mentions = dump.lineSequence() .filter { it.contains("com.peachpasswords.debug.test") } .take(10) .joinToString("\n") assertTrue( "no autofill session when 2FA field is focused -- picker collision regression.\n" + "dumpsys excerpt:\n$mentions", mentions.isNotEmpty(), ) } private fun focusField(contentDescription: String) { val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) val selector = By.desc(contentDescription) assertTrue("field not found: $contentDescription", device.wait(Until.hasObject(selector), 5_000)) device.findObject(selector).click() } }