@@ -86,6 +86,7 @@ const HASH_TO_SIDEBAR_LABEL = {
8686 '/settings' : 'Settings' ,
8787 '/intelligence' : 'Intelligence' ,
8888 '/channels' : 'Channels' ,
89+ '/rewards' : 'Rewards' ,
8990} ;
9091
9192export async function navigateViaHash ( hash ) {
@@ -105,56 +106,112 @@ export async function navigateViaHash(hash) {
105106 return ;
106107 }
107108
108- // Appium Mac2 — Settings → Billing (nested route)
109- if ( normalized === '/settings/billing' ) {
109+ // Appium Mac2 — Settings and sub-pages.
110+ //
111+ // Problem: "Settings" text appears on multiple pages (Home page card body,
112+ // Skills card descriptions, etc.), so clickText('Settings') often matches
113+ // the wrong element instead of the bottom tab bar button.
114+ //
115+ // Solution: target the tab bar button by its aria-label using XCUIElementTypeButton
116+ // XPath directly, which avoids matching StaticText elements on the page.
117+ if ( normalized === '/settings' || normalized . startsWith ( '/settings/' ) ) {
110118 try {
111- await clickText ( 'Settings' , 12_000 ) ;
112- await browser . pause ( 1_500 ) ;
113- const sub = await clickFirstMatch ( [ 'Billing & Usage' , 'Billing' ] , 12_000 ) ;
114- if ( ! sub ) {
115- throw new Error ( 'Mac2: could not find Billing / Billing & Usage after opening Settings' ) ;
119+ // Click the Settings tab button in the bottom bar using aria-label
120+ const settingsTabXpath =
121+ '//XCUIElementTypeButton[contains(@label, "Settings") or contains(@title, "Settings")]' ;
122+ const candidates = await browser . $$ ( settingsTabXpath ) ;
123+
124+ let clicked = false ;
125+ for ( const btn of candidates ) {
126+ try {
127+ const title = ( await btn . getAttribute ( 'title' ) ) || '' ;
128+ const label = ( await btn . getAttribute ( 'label' ) ) || '' ;
129+ // The tab bar button has a short title/label ("Settings"),
130+ // not a long description like settings menu items
131+ if (
132+ title === 'Settings' ||
133+ label === 'Settings' ||
134+ ( title . length < 20 && title . includes ( 'Settings' ) )
135+ ) {
136+ const loc = await btn . getLocation ( ) ;
137+ const size = await btn . getSize ( ) ;
138+ const cx = Math . round ( loc . x + size . width / 2 ) ;
139+ const cy = Math . round ( loc . y + size . height / 2 ) ;
140+ await browser . performActions ( [
141+ {
142+ type : 'pointer' ,
143+ id : 'mouse1' ,
144+ parameters : { pointerType : 'mouse' } ,
145+ actions : [
146+ { type : 'pointerMove' , duration : 10 , x : cx , y : cy } ,
147+ { type : 'pointerDown' , button : 0 } ,
148+ { type : 'pause' , duration : 50 } ,
149+ { type : 'pointerUp' , button : 0 } ,
150+ ] ,
151+ } ,
152+ ] ) ;
153+ await browser . releaseActions ( ) ;
154+ clicked = true ;
155+ console . log ( `[E2E] Mac2 clicked Settings tab button at (${ cx } , ${ cy } )` ) ;
156+ break ;
157+ }
158+ } catch {
159+ continue ;
160+ }
116161 }
117- await browser . pause ( 2_000 ) ;
118- console . log ( `[E2E] Mac2 navigated to ${ hash } via Settings → ${ sub } ` ) ;
119- } catch ( err ) {
120- const msg = err instanceof Error ? err . message : String ( err ) ;
121- throw new Error ( `[E2E] Mac2: failed to navigate to ${ hash } : ${ msg } ` ) ;
122- }
123- return ;
124- }
125162
126- // Appium Mac2 — nested settings routes via Skills built-in cards
127- const SKILLS_BUILTIN_ROUTES : Record < string , string [ ] > = {
128- '/settings/screen-intelligence' : [ 'Screen Intelligence' ] ,
129- '/settings/voice' : [ 'Voice Intelligence' , 'Voice Dictation' ] ,
130- '/settings/autocomplete' : [ 'Text Auto-Complete' , 'Inline Autocomplete' ] ,
131- } ;
132- const builtInLabels = SKILLS_BUILTIN_ROUTES [ normalized ] ;
133- if ( builtInLabels ) {
134- try {
135- // Navigate to Skills page first, then click the built-in card
136- await clickText ( 'Skills' , 12_000 ) ;
137- await browser . pause ( 2_000 ) ;
138- const sub = await clickFirstMatch ( builtInLabels , 12_000 ) ;
139- if ( ! sub ) {
140- // Fallback: try Settings sidebar → Automation menu item
163+ if ( ! clicked ) {
164+ // Fallback: try clickText
165+ console . log ( '[E2E] Mac2 Settings tab button not found, falling back to clickText' ) ;
141166 await clickText ( 'Settings' , 12_000 ) ;
142- await browser . pause ( 1_500 ) ;
143- const settingsSub = await clickFirstMatch ( builtInLabels , 12_000 ) ;
144- if ( ! settingsSub ) {
145- throw new Error (
146- `Mac2: could not find ${ builtInLabels . join ( ' / ' ) } in Skills or Settings`
147- ) ;
167+ }
168+
169+ await browser . pause ( 3_000 ) ;
170+ console . log ( `[E2E] Mac2 navigated to /settings` ) ;
171+
172+ // For sub-pages, click the menu item on the Settings home page
173+ if ( normalized !== '/settings' && normalized . startsWith ( '/settings/' ) ) {
174+ // Order: [section group, then item within that section]
175+ // Settings home → section page → specific panel
176+ const SETTINGS_SUB_ROUTES : Record < string , string [ ] > = {
177+ '/settings/billing' : [ 'Account & Security' , 'Billing & Usage' ] ,
178+ '/settings/recovery-phrase' : [ 'Account & Security' , 'Recovery Phrase' ] ,
179+ '/settings/team' : [ 'Account & Security' , 'Team' ] ,
180+ '/settings/connections' : [ 'Account & Security' , 'Connections' ] ,
181+ '/settings/screen-intelligence' : [ 'Automation & Channels' , 'Screen Intelligence' ] ,
182+ '/settings/messaging' : [ 'Automation & Channels' , 'Messaging Channels' ] ,
183+ '/settings/autocomplete' : [ 'Automation & Channels' , 'Inline Autocomplete' ] ,
184+ '/settings/cron-jobs' : [ 'Automation & Channels' , 'Cron Jobs' ] ,
185+ '/settings/local-model' : [ 'AI & Skills' , 'Local AI Model' ] ,
186+ '/settings/voice' : [ 'AI & Skills' , 'Voice Dictation' ] ,
187+ '/settings/ai' : [ 'AI & Skills' , 'AI Configuration' ] ,
188+ '/settings/tools' : [ 'AI & Skills' , 'Tools' ] ,
189+ '/settings/developer-options' : [ 'Developer Options' ] ,
190+ '/settings/memory-debug' : [ 'Developer Options' , 'Memory Debug' ] ,
191+ '/settings/webhooks-debug' : [ 'Developer Options' , 'Webhooks Debug' ] ,
192+ '/settings/privacy' : [ 'Privacy' ] ,
193+ } ;
194+
195+ const subLabels = SETTINGS_SUB_ROUTES [ normalized ] ;
196+ if ( subLabels ) {
197+ // Settings home shows section groups (Account & Security, etc.)
198+ // Each group is a button — click the section first, then the item
199+ for ( const label of subLabels ) {
200+ try {
201+ await clickText ( label , 8_000 ) ;
202+ await browser . pause ( 1_500 ) ;
203+ console . log ( `[E2E] Mac2 clicked Settings item: "${ label } "` ) ;
204+ } catch {
205+ console . log ( `[E2E] Mac2 Settings item "${ label } " not found, skipping` ) ;
206+ }
207+ }
208+ await browser . pause ( 1_500 ) ;
209+ console . log ( `[E2E] Mac2 navigated to ${ hash } ` ) ;
148210 }
149- await browser . pause ( 2_000 ) ;
150- console . log ( `[E2E] Mac2 navigated to ${ hash } via Settings → ${ settingsSub } ` ) ;
151- return ;
152211 }
153- await browser . pause ( 2_000 ) ;
154- console . log ( `[E2E] Mac2 navigated to ${ hash } via Skills → ${ sub } ` ) ;
155212 } catch ( err ) {
156213 const msg = err instanceof Error ? err . message : String ( err ) ;
157- throw new Error ( `[E2E] Mac2: failed to navigate to ${ hash } : ${ msg } ` ) ;
214+ console . log ( `[E2E] Mac2: failed to navigate to ${ hash } : ${ msg } ` ) ;
158215 }
159216 return ;
160217 }
0 commit comments