From e0ab9d2a80c44a8074860d0d6fc88f04cc1e683c Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 5 Feb 2026 10:38:46 -0800 Subject: [PATCH 01/13] fix: Add pointerEvents='none' to Icon and FloatingActionButton to prevent SVG click interception Browser automation tools (and potentially users) have trouble clicking on certain UI elements because SVG icons inside buttons/pressables intercept clicks. The SVG path elements become the click target instead of the parent pressable. This fix adds pointerEvents="none" to: - Icon component's View wrapper and ImageSVG (both main and inline rendering) - FloatingActionButton's Svg component Co-authored-by: Cursor --- src/components/FloatingActionButton.tsx | 1 + src/components/Icon/index.tsx | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index 9ae6947be266e..9b85fa264179f 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -124,6 +124,7 @@ function FloatingActionButton({onPress, onLongPress, isActive, accessibilityLabe @@ -182,6 +184,7 @@ function Icon({ accessibilityElementsHidden importantForAccessibility="no-hide-descendants" accessible={false} + pointerEvents="none" > ); From 44772955ed185011409462b7985af65d3737f3be Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 12 Feb 2026 08:54:26 -0800 Subject: [PATCH 02/13] Provide names for onboarding radio buttons for test automation --- src/components/RadioButtonWithLabel.tsx | 2 +- src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/RadioButtonWithLabel.tsx b/src/components/RadioButtonWithLabel.tsx index 1be91307b9ac5..5926f4ec8f663 100644 --- a/src/components/RadioButtonWithLabel.tsx +++ b/src/components/RadioButtonWithLabel.tsx @@ -81,7 +81,7 @@ function RadioButtonWithLabel({ pressDimmingValue={0.5} shouldBlendOpacity={shouldBlendOpacity} > - {!!label && ( + {!!label && !labelElement && ( handleIntegrationSelect(item.keyForList)} + label={item.text} style={[styles.flexRowReverse]} wrapperStyle={[styles.ml0]} labelElement={ From fa4e03997acf3a8a95c19c993b4efee5f87a7792 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 12 Feb 2026 09:02:12 -0800 Subject: [PATCH 03/13] Speed up manual testing --- .claude/skills/playwright-app-testing/SKILL.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.claude/skills/playwright-app-testing/SKILL.md b/.claude/skills/playwright-app-testing/SKILL.md index 1e8abfa132dfb..d9d62d52aa6c6 100644 --- a/.claude/skills/playwright-app-testing/SKILL.md +++ b/.claude/skills/playwright-app-testing/SKILL.md @@ -32,12 +32,23 @@ ps aux | grep "webpack" | grep -v grep ## Playwright Testing Workflow 1. **Verify server**: Check webpack process is running -2. **Navigate**: Use `mcp__playwright__browser_navigate` to `https://dev.new.expensify.com:8082/` -3. **Interact**: Use Playwright MCP tools including: +2. **Navigate**: Use `browser_navigate` to `https://dev.new.expensify.com:8082/` +3. **Interact**: Use browser MCP tools including: - **Inspection**: `browser_snapshot`, `browser_take_screenshot`, `browser_console_messages` - **Interaction**: `browser_click`, `browser_type`, `browser_fill_form`, `browser_hover` - **Navigation**: `browser_navigate_back`, `browser_tabs`, `browser_wait_for` - - All other Playwright tools as needed + - All other browser tools as needed + +## Speed Optimization: No Pre-Waiting + +**CRITICAL**: Do NOT add arbitrary waits (`browser_wait_for` with a time) after actions like clicks, fills, or navigation. Instead, follow this pattern: + +1. **Perform the action** (click, type, fill, etc.) +2. **Immediately take a snapshot** (`browser_snapshot`) to check the result. +3. **If the page appears unchanged** (same elements, same URL), wait 1 second and snapshot again. +4. **Repeat up to 3 times** with 1-second waits if needed. + +This approach is significantly faster than adding 3-4 second waits after every action. Most actions in the app complete in under 500ms, so immediate snapshots will usually capture the result. ## Dev Environment Sign-In From a9aef7b3f2d5b4237bce8c16f50a50cb80789932 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 12 Feb 2026 09:49:54 -0800 Subject: [PATCH 04/13] Remove presentation role to make inputs discoverable for automation --- src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx | 2 -- src/pages/iou/request/step/IOURequestStepDescription.tsx | 1 - src/pages/iou/request/step/IOURequestStepMerchant.tsx | 1 - 3 files changed, 4 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx b/src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx index 6617ab9bb87a2..7f3197e3cc78b 100644 --- a/src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx +++ b/src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx @@ -114,7 +114,6 @@ function IOURequestStepCompanyInfo({route, report, transaction}: IOURequestStepC name={INPUT_IDS.COMPANY_NAME} label={translate('iou.yourCompanyName')} accessibilityLabel={translate('iou.yourCompanyName')} - role={CONST.ROLE.PRESENTATION} ref={inputCallbackRef} containerStyles={styles.mv4} /> @@ -125,7 +124,6 @@ function IOURequestStepCompanyInfo({route, report, transaction}: IOURequestStepC inputMode={CONST.INPUT_MODE.URL} label={translate('iou.yourCompanyWebsite')} accessibilityLabel={translate('iou.yourCompanyWebsite')} - role={CONST.ROLE.PRESENTATION} hint={translate('iou.yourCompanyWebsiteNote')} defaultValue={defaultWebsiteExample} /> diff --git a/src/pages/iou/request/step/IOURequestStepDescription.tsx b/src/pages/iou/request/step/IOURequestStepDescription.tsx index efaecde259e76..f59610ff1791e 100644 --- a/src/pages/iou/request/step/IOURequestStepDescription.tsx +++ b/src/pages/iou/request/step/IOURequestStepDescription.tsx @@ -209,7 +209,6 @@ function IOURequestStepDescription({ onValueChange={updateDescriptionRef} label={translate('moneyRequestConfirmationList.whatsItFor')} accessibilityLabel={translate('moneyRequestConfirmationList.whatsItFor')} - role={CONST.ROLE.PRESENTATION} autoGrowHeight maxAutoGrowHeight={variables.textInputAutoGrowMaxHeight} shouldSubmitForm diff --git a/src/pages/iou/request/step/IOURequestStepMerchant.tsx b/src/pages/iou/request/step/IOURequestStepMerchant.tsx index 9e085ef276ee7..9e95081825749 100644 --- a/src/pages/iou/request/step/IOURequestStepMerchant.tsx +++ b/src/pages/iou/request/step/IOURequestStepMerchant.tsx @@ -167,7 +167,6 @@ function IOURequestStepMerchant({ onValueChange={updateMerchantRef} label={translate('common.merchant')} accessibilityLabel={translate('common.merchant')} - role={CONST.ROLE.PRESENTATION} ref={inputCallbackRef} /> From e4dd33f665213369ed90e5f73128bc01dac9c779 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 17 Feb 2026 06:41:52 -0800 Subject: [PATCH 05/13] Remove useless sentence per feedback --- .claude/skills/playwright-app-testing/SKILL.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/.claude/skills/playwright-app-testing/SKILL.md b/.claude/skills/playwright-app-testing/SKILL.md index d9d62d52aa6c6..0a7612bc4eada 100644 --- a/.claude/skills/playwright-app-testing/SKILL.md +++ b/.claude/skills/playwright-app-testing/SKILL.md @@ -48,8 +48,6 @@ ps aux | grep "webpack" | grep -v grep 3. **If the page appears unchanged** (same elements, same URL), wait 1 second and snapshot again. 4. **Repeat up to 3 times** with 1-second waits if needed. -This approach is significantly faster than adding 3-4 second waits after every action. Most actions in the app complete in under 500ms, so immediate snapshots will usually capture the result. - ## Dev Environment Sign-In When signing in to dev environment: From 72bb323fb873ecc8873c24b255a08513ee1dde03 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 17 Feb 2026 06:52:59 -0800 Subject: [PATCH 06/13] Add missing sentryLabel props to fix ESLint errors Co-authored-by: Cursor --- src/components/RadioButtonWithLabel.tsx | 1 + src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/RadioButtonWithLabel.tsx b/src/components/RadioButtonWithLabel.tsx index 5926f4ec8f663..3d769b5e8527c 100644 --- a/src/components/RadioButtonWithLabel.tsx +++ b/src/components/RadioButtonWithLabel.tsx @@ -71,6 +71,7 @@ function RadioButtonWithLabel({ hasError={hasError} /> ( handleIntegrationSelect(item.keyForList)} accessibilityLabel={item.text} From 8db19a353fdee7f07653e32c79c46482204ebc8b Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 17 Feb 2026 07:29:06 -0800 Subject: [PATCH 07/13] Add accessibility label to radio button with label --- src/components/RadioButtonWithLabel.tsx | 8 ++++++-- .../OnboardingAccounting/BaseOnboardingAccounting.tsx | 3 +-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/RadioButtonWithLabel.tsx b/src/components/RadioButtonWithLabel.tsx index 3d769b5e8527c..b61cf7f5c61c4 100644 --- a/src/components/RadioButtonWithLabel.tsx +++ b/src/components/RadioButtonWithLabel.tsx @@ -25,6 +25,9 @@ type RadioButtonWithLabelProps = ForwardedFSClassProps & { /** React element to display for the label */ labelElement?: ReactNode; + /** Specifies the accessibility label for the radio button. Falls back to label if not provided. */ + accessibilityLabel?: string; + /** Should the input be styled for errors */ hasError?: boolean; @@ -47,6 +50,7 @@ function RadioButtonWithLabel({ labelElement, style, label = '', + accessibilityLabel, hasError = false, errorText = '', isChecked, @@ -67,7 +71,7 @@ function RadioButtonWithLabel({ - {!!label && !labelElement && ( + {!!label && ( handleIntegrationSelect(item.keyForList)} - accessibilityLabel={item.text} accessible={false} hoverStyle={!item.isSelected ? styles.hoveredComponentBG : undefined} style={[styles.onboardingAccountingItem, isSmallScreenWidth && styles.flexBasis100, item.isSelected && styles.activeComponentBG]} @@ -207,7 +206,7 @@ function BaseOnboardingAccounting({shouldUseNativeStyles, route}: BaseOnboarding handleIntegrationSelect(item.keyForList)} - label={item.text} + accessibilityLabel={item.text} style={[styles.flexRowReverse]} wrapperStyle={[styles.ml0]} labelElement={ From 1b8baefa328f17ad02ad37b0a23d8abc8db84d7c Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 17 Feb 2026 07:42:40 -0800 Subject: [PATCH 08/13] Root cause fix role prop incorrectly set on child input via the base component --- .../TextInput/BaseTextInput/implementation/index.tsx | 4 ++++ src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx | 2 ++ src/pages/iou/request/step/IOURequestStepDescription.tsx | 1 + src/pages/iou/request/step/IOURequestStepMerchant.tsx | 1 + 4 files changed, 8 insertions(+) diff --git a/src/components/TextInput/BaseTextInput/implementation/index.tsx b/src/components/TextInput/BaseTextInput/implementation/index.tsx index 5e1574fd36fd2..eee0f6b5c7dbf 100644 --- a/src/components/TextInput/BaseTextInput/implementation/index.tsx +++ b/src/components/TextInput/BaseTextInput/implementation/index.tsx @@ -84,6 +84,10 @@ function BaseTextInput({ shouldUseDefaultLineHeightForPrefix = true, ref, sentryLabel, + + // Destructure role so it doesn't leak into inputProps and override the + // native semantics of the actual