Skip to content

Compose CardFields Instrumentations Tests - #1692

Open
noguier wants to merge 14 commits into
compose-autoadvancefrom
compose-cardFields-instrumentation
Open

Compose CardFields Instrumentations Tests#1692
noguier wants to merge 14 commits into
compose-autoadvancefrom
compose-cardFields-instrumentation

Conversation

@noguier

@noguier noguier commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Summary of changes

  • Created tests for each individual input field: cc number, expiration date, cvv
  • Created tests for all fields put together to test autoadvance and the form validation

AI Usage

Which AI Agent Was Used?

  • Copilot
  • Claude
  • Other (Type Name Here)

How was AI used?
Brainstroming and getting familiar with syntax to use to test Compose

Estimated AI Code Contribution

  • less than 30%
  • 30 - 60%
  • 60 - 100%

Checklist

  • Added a changelog entry
  • Tested and confirmed payment flows affected by this change are functioning as expected

Authors

List GitHub usernames for everyone who contributed to this pull request.
@noguier

@noguier
noguier requested a review from a team as a code owner August 31, 2026 19:04
Copilot AI lite review requested due to automatic review settings August 31, 2026 19:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The newly added instrumentation tests have compile-breaking missing imports (and one test name/behavior mismatch) that should be corrected before merge.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds Jetpack Compose instrumentation coverage for the UIComponents CardFields Compose form, validating individual input behavior (card number, expiration, CVV) and cross-field interactions (auto-advance + validation-on-blur). This fits into the UIComponents module’s Compose-based card entry experience by improving confidence in accessibility-driven UI interactions and field state transitions.

Changes:

  • Added new androidTest Compose UI tests for CardNumberField, CardExpirationField, and CardCvvField.
  • Added a form-level CardFields test suite covering auto-advance focus behavior and validation error display rules.
  • Updated UIComponents build + version catalog to include Compose UI test dependencies.
File summaries
File Description
UIComponents/src/androidTest/java/com/braintreepayments/api/uicomponents/compose/CardNumberFieldTest.kt Adds instrumentation tests for card number input + brand icon detection.
UIComponents/src/androidTest/java/com/braintreepayments/api/uicomponents/compose/CardFieldsFormTest.kt Adds end-to-end form tests for focus auto-advance and validation-on-blur behavior.
UIComponents/src/androidTest/java/com/braintreepayments/api/uicomponents/compose/CardExpirationFieldTest.kt Adds instrumentation tests for expiration input sanitization/formatting behavior.
UIComponents/src/androidTest/java/com/braintreepayments/api/uicomponents/compose/CardCvvFieldTest.kt Adds instrumentation tests for CVV input and the hint overlay UI.
UIComponents/build.gradle Adds Compose UI test dependencies for androidTest and ui-test-manifest for debug.
gradle/libs.versions.toml Adds version-catalog entries for Compose UI testing artifacts.
Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copilot AI review requested due to automatic review settings August 31, 2026 20:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The new androidTests currently have compilation/runner consistency issues (e.g., missing assertDoesNotExist import and missing @RunWith(AndroidJUnit4ClassRunner::class) in the new test classes).

Review details

Suppressed comments (5)

Previously missed (3) — in code that hasn't changed since the last review.

UIComponents/src/androidTest/java/com/braintreepayments/api/uicomponents/compose/CardFieldsFormTest.kt:24

  • Add the AndroidJUnit4 test runner annotation (consistent with other UIComponents instrumentation tests, e.g. cardfields/CardFieldsTest.kt) so this test runs under the same runner configuration.
class CardFieldsFormTest {

UIComponents/src/androidTest/java/com/braintreepayments/api/uicomponents/compose/CardNumberFieldTest.kt:20

  • UIComponents instrumentation tests in this repo consistently declare @RunWith(AndroidJUnit4ClassRunner::class) (e.g. uicomponents/cardfields/CardFieldsTest.kt). Adding it here keeps runner behavior consistent for these Compose androidTests.
import androidx.test.platform.app.InstrumentationRegistry
import com.braintreepayments.api.uicomponents.R
import com.braintreepayments.api.uicomponents.cardfields.CardBrand
import org.junit.Rule
import org.junit.Test

UIComponents/src/androidTest/java/com/braintreepayments/api/uicomponents/compose/CardExpirationFieldTest.kt:18

  • UIComponents instrumentation tests in this repo consistently declare @RunWith(AndroidJUnit4ClassRunner::class) (e.g. uicomponents/cardfields/CardFieldsTest.kt). Adding it here keeps runner behavior consistent for these Compose androidTests.
import androidx.test.platform.app.InstrumentationRegistry
import com.braintreepayments.api.uicomponents.R
import org.junit.Rule
import org.junit.Test

UIComponents/src/androidTest/java/com/braintreepayments/api/uicomponents/compose/CardFieldsFormTest.kt:13

  • assertDoesNotExist() is used in this test file but the corresponding Compose test import is missing, and the file also lacks the @RunWith(AndroidJUnit4ClassRunner::class) annotation used by other UIComponents androidTest classes (e.g. cardfields/CardFieldsTest.kt). This will currently fail compilation and may run under a different runner than the rest of the module.
import androidx.compose.ui.test.performTextInput
import androidx.test.platform.app.InstrumentationRegistry
import com.braintreepayments.api.uicomponents.R
import org.junit.Rule
import org.junit.Test

UIComponents/src/androidTest/java/com/braintreepayments/api/uicomponents/compose/CardCvvFieldTest.kt:20

  • assertDoesNotExist() is used but not imported, and the class is missing the @RunWith(AndroidJUnit4ClassRunner::class) annotation that other UIComponents androidTest classes use. This will fail compilation and makes the runner inconsistent.
import androidx.test.platform.app.InstrumentationRegistry
import com.braintreepayments.api.uicomponents.R
import org.junit.Rule
import org.junit.Test

  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@saralvasquez saralvasquez left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a couple test cases I think could be good to add but otherwise these look great!

}

@Test
fun tappingCloseButton_dismissesHintPopup() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also want a test that clicking anywhere on the screen outside the popup will dismiss it? I recall that being important functionality

import org.junit.Test

/**
* Cross-field behavior owned by the [CardFields] composable + `CardFieldsViewModel`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed we don't have a test to see that the pay button becomes enabled after all fields are valid. Is that something that's testable since it's not technically part of card fields?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I was contemplating about that myself as well. Since the button is technically owned by a merchant, but i see that XML cardFields do have a test for the button, so I can add it here as well.

}

@Test
fun typingDigits_updatesFieldText() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want a test here that passes a value that's too long like we do for expiration?

import org.junit.Rule
import org.junit.Test

class CardCvvFieldTest {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also want a test here that passes a value that's too long like for expiration? We also might want to check that the max value changes with the right card brand. But that might be better in the CardFieldsFormTest. What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both of these scenarios are good to add. I think I can add a test for long value here, but the test for length change according to the brand in CardFieldsFormTest

composeTestRule.onNodeWithContentDescription(str(R.string.cvv_accessibility))
.performTextInput("123")

composeTestRule.onNodeWithContentDescription(str(R.string.cvv_accessibility))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly dumb question: What is R?

@noguier noguier Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great question, R is autogenerated class, that assigns an id to your resources: https://developer.android.com/guide/topics/resources/providing-resources

so here we are just grabbing the string we defined that has an accessibility text from strings_card_fields.xml

@noguier noguier mentioned this pull request Sep 2, 2026
8 tasks
Copilot AI review requested due to automatic review settings September 2, 2026 16:09
@noguier
noguier force-pushed the compose-cardFields-instrumentation branch from bc55fd8 to 6dcd26d Compare September 2, 2026 16:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

Changes are additive test coverage + scoped dependency additions, with no impact to the shipped runtime behavior.

Review details

Suppressed comments (1)

UIComponents/src/androidTest/java/com/braintreepayments/api/uicomponents/compose/CardFieldsFormTest.kt:20

  • In the KDoc bullet list, there’s an extra space before the colon in “Validation state machine :”, which reads like a typo.
 * 2. **Validation state machine :** while the user is typing, an incomplete value
  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 2, 2026 20:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

One of the UIAutomator-based dismissal tests relies on a bottom-of-screen coordinate that can land on the system navigation bar and make CI runs flaky.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +89 to +92
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
// Tap anywhere on the screen outside the popup to dismiss it
device.click(device.displayWidth / 2, device.displayHeight - 100)

Comment thread gradle/libs.versions.toml
androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidxTest" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxJunit" }
androidx-test-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso" }
androidx-test-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this needed to test Compose features/functionality? It seems like a new/different way of writing Android tests so I'm curious how the team usually goes about adopting new dependencies like this one.

I also see that there is a newer version out, albeit an alpha. The docs do say:

The API is under development, and we strongly recommend using it for any new development with UI Automator.

Maybe we should consider using the newer version in the style that Google is going to be developing for moving forward since we're introducing here for the first time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed to add this to test the behavior of tapping anywhere on the screen to close the cvv info popup, and I did tried to use 2.4.0, but that version resulted in
e: Class 'kotlin.reflect.KClass' was compiled with an incompatible version of Kotlin. The actual metadata version is 2.1.0, but the compiler version 1.9.0 can read versions up to 2.0.0.
So in order to use a new version, we would need to update Kotlin to at least 2.1.0. So I opted to using the olde version. Thoughts?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I guess we'll be stuck using the older one until we can determine if/when we can upgrade to Kotlin 2+

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants