Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix voice over for invalid input #10272

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

cttsai-stripe
Copy link
Contributor

Summary

https://jira.corp.stripe.com/browse/MOBILESDK-2675
https://jira.corp.stripe.com/browse/MOBILESDK-2676
Use the error message shown on UI instead of the default one for voice over.

Motivation

#9525

The inline error message should be focused and announced asap when a user enters an invalid card number or expiry date.
When the user navigates back to the card number or expiration date field with the left swipe gesture, the VO of the inline error message should be announced exactly as it is displayed along with the value that is “Edit box: 424242… Card number. Your card number is invalid“ not the “Edit box: 424242…Card number. Error: Invalid input“.

Testing

  • Added tests
  • Modified tests
  • Manually verified

Screenshots

Before After
before screenshot Screen_recording_20250226_114333.webm

Changelog

Copy link
Contributor

github-actions bot commented Feb 26, 2025

Diffuse output:

OLD: paymentsheet-example-release-master.apk (signature: V1, V2)
NEW: paymentsheet-example-release-pr.apk (signature: V1, V2)

          │           compressed           │         uncompressed         
          ├───────────┬───────────┬────────┼──────────┬──────────┬────────
 APK      │ old       │ new       │ diff   │ old      │ new      │ diff   
──────────┼───────────┼───────────┼────────┼──────────┼──────────┼────────
      dex │   4.1 MiB │   4.1 MiB │ +505 B │    9 MiB │    9 MiB │ +832 B 
     arsc │   2.4 MiB │   2.4 MiB │    0 B │  2.4 MiB │  2.4 MiB │    0 B 
 manifest │   5.1 KiB │   5.1 KiB │    0 B │ 25.7 KiB │ 25.7 KiB │    0 B 
      res │ 910.9 KiB │ 910.9 KiB │    0 B │  1.4 MiB │  1.4 MiB │    0 B 
   native │   2.6 MiB │   2.6 MiB │    0 B │    6 MiB │    6 MiB │    0 B 
    asset │   1.6 MiB │   1.6 MiB │   -2 B │  1.6 MiB │  1.6 MiB │   -2 B 
    other │   1.4 MiB │   1.4 MiB │  +12 B │  1.6 MiB │  1.6 MiB │    0 B 
──────────┼───────────┼───────────┼────────┼──────────┼──────────┼────────
    total │  12.9 MiB │  12.9 MiB │ +515 B │ 22.1 MiB │ 22.1 MiB │ +830 B 

 DEX     │ old   │ new   │ diff           
─────────┼───────┼───────┼────────────────
   files │     1 │     1 │  0             
 strings │ 42783 │ 42784 │ +1 (+15 -14)   
   types │ 15351 │ 15352 │ +1 (+9 -8)     
 classes │ 12958 │ 12959 │ +1 (+2 -1)     
 methods │ 62692 │ 62694 │ +2 (+693 -691) 
  fields │ 41698 │ 41704 │ +6 (+636 -630) 

 ARSC    │ old  │ new  │ diff 
─────────┼──────┼──────┼──────
 configs │  243 │  243 │  0   
 entries │ 6273 │ 6273 │  0
APK
    compressed     │    uncompressed    │                               
──────────┬────────┼───────────┬────────┤                               
 size     │ diff   │ size      │ diff   │ path                          
──────────┼────────┼───────────┼────────┼───────────────────────────────
  4.1 MiB │ +505 B │     9 MiB │ +832 B │ ∆ classes.dex                 
   54 KiB │  +10 B │   120 KiB │    0 B │ ∆ META-INF/CERT.SF            
  8.1 KiB │   -2 B │     8 KiB │   -2 B │ ∆ assets/dexopt/baseline.prof 
 50.6 KiB │   +2 B │ 119.9 KiB │    0 B │ ∆ META-INF/MANIFEST.MF        
──────────┼────────┼───────────┼────────┼───────────────────────────────
  4.2 MiB │ +515 B │   9.3 MiB │ +830 B │ (total)
DEX
STRINGS:

   old   │ new   │ diff         
  ───────┼───────┼──────────────
   42783 │ 42784 │ +1 (+15 -14) 
  
  + Lf9/z1;
  + Lk6/c;
  + VLLLZLLLLZLLLLZIILLLI
  + VLLLZLLLLZLLLLZIILLLLI
  + VLLLZLLLZLLLLLZZZL
  + VLZZLLLZZLZLLLLLLLI
  + VLZZLLLZZLZLLLLLLLLI
  + [Lf9/J0;
  + [Lf9/M;
  + [Lf9/N0;
  + [Lf9/P0;
  + [Lf9/P;
  + [Lf9/c0;
  + [Lf9/w0;
  + ~~R8{"backend":"dex","compilation-mode":"release","has-checksums":false,"min-api":21,"pg-map-id":"485c076","r8-mode":"full","version":"8.8.27"}
  
  - Li6/m;
  - VLLLZLLLLZLLLZIILLLI
  - VLLLZLLLLZLLLZIILLLLI
  - VLLLZLLLZLLLLZZZL
  - VLZZLLLZZZLLLLLLLI
  - VLZZLLLZZZLLLLLLLLI
  - [Lf9/I0;
  - [Lf9/K;
  - [Lf9/M0;
  - [Lf9/O0;
  - [Lf9/O;
  - [Lf9/a0;
  - [Lf9/v0;
  - ~~R8{"backend":"dex","compilation-mode":"release","has-checksums":false,"min-api":21,"pg-map-id":"5720b56","r8-mode":"full","version":"8.8.27"}
  

TYPES:

   old   │ new   │ diff       
  ───────┼───────┼────────────
   15351 │ 15352 │ +1 (+9 -8) 
  
  + Lf9/z1;
  + Lk6/c;
  + [Lf9/J0;
  + [Lf9/M;
  + [Lf9/N0;
  + [Lf9/P0;
  + [Lf9/P;
  + [Lf9/c0;
  + [Lf9/w0;
  
  - Li6/m;
  - [Lf9/I0;
  - [Lf9/K;
  - [Lf9/M0;
  - [Lf9/O0;
  - [Lf9/O;
  - [Lf9/a0;
  - [Lf9/v0;
  

METHODS:

   old   │ new   │ diff           
  ───────┼───────┼────────────────
   62692 │ 62694 │ +2 (+693 -691) 
  
  + A7.M <init>(p, Function0, Function0, Function0, V, p)
  + A7.o0 <init>(Z, Z0)
  + B8.I r(String) → f1
  + D0.c H(w, boolean, Z0, l0, Function1, Function1, Function0, Function0, Function0, Function0, Function1, Function1, Function1, Function0, p, int, int)
  + D0.c h(v, Z0, l0, boolean, m, p, int)
  + D0.c r(Integer, Z0, g0, Z0, c, boolean, boolean, boolean, j, boolean, Function0, m, p, p, p, p, int, int, int)
  + D0.c v(LayoutWeightElement, w, boolean, Z0, l0, Function1, Function1, Function0, Function1, Function1, Function1, Function0, p, int, int)
  + F.q <init>(Function0, boolean)
  + F6.d <init>(long, long, long, long, long, long, long, long, long, long, Z, long, long, long, long, W)
  + G6.H <init>(g0, Z0, b, Function0, int)
  + G6.a0 <init>(String, boolean)
  + G6.k <init>(c, F0, Map)
  + G6.t <init>(p, q, boolean, int)
  + H6.A <init>(Z0, g0, Z0, c, boolean, boolean, j, Z, p, Z)
  + H6.F <init>(boolean, G0, Set, P, p, int, int, int)
  + H6.F <init>(boolean, Z0, c, int, p, a, int, int)
  + H6.I <init>(c, Z0, boolean, p, p, p, boolean, Continuation)
  + H6.p <init>(Integer, Z0, g0, Z0, c, boolean, boolean, boolean, j, boolean, Function0, m, p, p, p, int, int, int)
  + H6.s <init>(Function0, boolean, boolean, Z0, c, boolean, p, g0, p, Z0, p, j)
  + H6.t <init>(boolean, c, boolean, g0, p, Z0, p, j, boolean, Function0, boolean, Z0, p)
  + H6.v <init>(String, j, Z0, g0, Z0, c, boolean, boolean, boolean, j, Function0, p, int, int, int)
  + H6.x <init>(j, Z0, g0, Z0, boolean, c, boolean, boolean, j, p, int)
  + H6.x <init>(boolean, a, boolean, boolean, T0, T0, g0, a, P, F0, int)
  + H6.x <init>(boolean, boolean, c, boolean, j, j, Z0, g0, Z0, p, int)
  + L0.h a(l0)
  + L0.h d(l0)
  + L5.L <init>(p, V, boolean, Throwable, p, int)
  + L5.N G(p, V, boolean, Throwable, p, p, int)
  + L5.N o(Set, boolean, List, P, p, p, int)
  + L5.N q(M, int, int, boolean, Set, P, p, int)
  + L5.N w(boolean, boolean, c, boolean, j, j, Z0, g0, Z0, p, p, int)
  + L5.N y(String, j, Z0, g0, Z0, c, boolean, boolean, boolean, j, Function0, p, p, int, int, int)
  + L5.w <init>(p, boolean, Z0, Z)
  + M6.l <init>(v, Z0, l0, boolean, m, int)
  + M6.l <init>(boolean, L0, Object, Object, p, int, int)
  + M6.q <init>(w, boolean, Z0, l0, Function1, Function1, Function0, Function0, Function0, Function0, Function1, Function1, Function1, Function0, int, int, int)
  + M6.r <init>(LayoutWeightElement, w, boolean, Z0, l0, Function1, Function1, Function0, Function1, Function1, Function1, Function0, int, int)
  + P6.a b(Map) → G0
  + P6.b b(Map) → G0
  + P6.c b(Map) → G0
  + P6.d b(Map) → G0
  + S6.a a() → P
  + S8.d <init>(M, int, int, boolean, Set, P, int)
  + T5.d <init>(String, String, V, String)
  + V.a G(CoroutineContext, l0, Function2, ContinuationImpl) → Object
  + V.a H(c, String, y) → boolean
  + V.a a(p, q, boolean, p, int)
  + V.a l(boolean, a, Set, P, p, int)
  + V.b e(Z0, boolean, boolean, p, p, int)
  + V.c O(boolean, G0, Set, P, p, int, int, p, int)
  + V.d M(i, m, a, float, long, long, long, a, p, int)
  + V.d Q(boolean, J0, p, Set, P, int, int, p, int, int)
  + V.e p(boolean, V, p, a, L, float, float, String, W, p, p, int, int)
  + V.e q(String, boolean
...✂

@@ -139,6 +140,7 @@ internal fun CompatTextField(
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
isError: Boolean = false,
errorString: String?,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that typically we call this errorMessage or just error, the String part is implied from the type. Can you update the name here and elsewhere?

Also, on this one can you update the javadoc? We have javadoc for all the other fields so it'd be nice to keep that up

@@ -451,4 +453,38 @@ internal class PaymentSheetTest {

testContext.markTestSucceeded()
}

@Test
fun testCardDetailsErrorMessageAccessibility() = runPaymentSheetTest(
Copy link
Collaborator

Choose a reason for hiding this comment

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

a couple ideas to improve this:

  • could we have separate tests for the card number field and the expiration date field? if the card number field case started failing, the test would stop at that assert and we wouldn't immediately know whether the expiration date field case was also failing
  • wdyt of including these tests in a non-network test test class? These tests take a long time to run and are also more prone to flakiness. We really don't need to test any of the set up (ie the elements session response) to test this case, so I think this might be more appropriate to test in something more similar to a unit test, e.g. VerticalModeFormUITest

Copy link
Contributor Author

Choose a reason for hiding this comment

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

VerticalModeFormUITest seems reasonable. I am not sure if this is the best place but I didn't find a better one though.

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.

2 participants