@@ -22,9 +22,7 @@ export async function dumpUiHierarchy(): Promise<string> {
2222 }
2323}
2424
25- export async function findElementByText ( text : string , exactMatch : boolean = false ) : Promise < { x : number ; y : number } | null > {
26- const xml = await dumpUiHierarchy ( ) ;
27-
25+ function findTextInXml ( xml : string , text : string , exactMatch : boolean = false ) : { x : number ; y : number } | null {
2826 const escapedText = text . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
2927 const pattern = exactMatch
3028 ? new RegExp ( `text="${ escapedText } "[^>]*bounds="\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]"` )
@@ -49,6 +47,11 @@ export async function findElementByText(text: string, exactMatch: boolean = fals
4947 return null ;
5048}
5149
50+ export async function findElementByText ( text : string , exactMatch : boolean = false ) : Promise < { x : number ; y : number } | null > {
51+ const xml = await dumpUiHierarchy ( ) ;
52+ return findTextInXml ( xml , text , exactMatch ) ;
53+ }
54+
5255export async function findElementByResourceId ( resourceId : string ) : Promise < { x : number ; y : number } | null > {
5356 const xml = await dumpUiHierarchy ( ) ;
5457
@@ -100,15 +103,12 @@ export async function pressBack(): Promise<void> {
100103
101104async function setupScreenLockViaUi ( pin : string = '1234' ) : Promise < boolean > {
102105 try {
103- console . log ( ' Attempting to set up screen lock via UI...' ) ;
104-
105106 let tapped = await tapText ( 'Set screen lock' , true ) ;
106107 if ( ! tapped ) {
107108 tapped = await tapText ( 'Set a screen lock' , false ) ;
108109 }
109110
110111 if ( ! tapped ) {
111- console . log ( ' Could not find "Set screen lock" button' ) ;
112112 return false ;
113113 }
114114
@@ -120,7 +120,6 @@ async function setupScreenLockViaUi(pin: string = '1234'): Promise<boolean> {
120120 }
121121
122122 if ( ! tapped ) {
123- console . log ( ' Could not find PIN option' ) ;
124123 await pressBack ( ) ;
125124 return false ;
126125 }
@@ -153,10 +152,8 @@ async function setupScreenLockViaUi(pin: string = '1234'): Promise<boolean> {
153152 await delay ( 500 ) ;
154153 }
155154
156- console . log ( ' Screen lock setup via UI complete' ) ;
157155 return true ;
158- } catch ( e ) {
159- console . log ( ' Error setting up screen lock via UI:' , e ) ;
156+ } catch {
160157 return false ;
161158 }
162159}
@@ -167,15 +164,13 @@ async function setupScreenLockViaUi(pin: string = '1234'): Promise<boolean> {
167164 */
168165export async function handleVpnPermissionDialog ( timeoutMs : number = 15000 ) : Promise < boolean > {
169166 const startTime = Date . now ( ) ;
170- let lastXml = '' ;
171167
172168 while ( Date . now ( ) - startTime < timeoutMs ) {
173169 if ( await checkVpnActive ( ) ) {
174170 return true ;
175171 }
176172
177173 const xml = await dumpUiHierarchy ( ) ;
178- lastXml = xml ;
179174
180175 if ( xml . toLowerCase ( ) . includes ( 'connected' ) &&
181176 xml . toLowerCase ( ) . includes ( 'disconnect' ) &&
@@ -191,9 +186,9 @@ export async function handleVpnPermissionDialog(timeoutMs: number = 15000): Prom
191186
192187 if ( xml . toLowerCase ( ) . includes ( 'send you notifications' ) ||
193188 ( xml . toLowerCase ( ) . includes ( 'notification' ) && xml . toLowerCase ( ) . includes ( 'allow' ) ) ) {
194- console . log ( ' Found notification permission dialog, tapping Allow...' ) ;
195- if ( await tapText ( 'Allow' , true ) ) {
196- console . log ( ' Tapped Allow on notification dialog' ) ;
189+ const allowButton = findTextInXml ( xml , ' Allow' , true ) ;
190+ if ( allowButton ) {
191+ await tap ( allowButton . x , allowButton . y ) ;
197192 await delay ( 1000 ) ;
198193 continue ;
199194 }
@@ -222,41 +217,39 @@ export async function handleVpnPermissionDialog(timeoutMs: number = 15000): Prom
222217 xml . toLowerCase ( ) . includes ( 'set up vpn' ) ;
223218
224219 if ( xml . toLowerCase ( ) . includes ( 'manual setup required' ) ) {
225- console . log ( ' Found "Manual setup required" dialog from app' ) ;
226-
227- if ( await tapText ( 'Skip' , true ) ) {
228- console . log ( ' Tapped Skip on manual setup dialog' ) ;
220+ const skipButton = findTextInXml ( xml , 'Skip' , true ) ;
221+ if ( skipButton ) {
222+ await tap ( skipButton . x , skipButton . y ) ;
229223 await delay ( 2000 ) ;
230224
225+ if ( await checkVpnActive ( ) ) {
226+ return true ;
227+ }
228+
231229 const afterSkipXml = await dumpUiHierarchy ( ) ;
232230 if ( afterSkipXml . toLowerCase ( ) . includes ( 'connection request' ) ||
233231 afterSkipXml . toLowerCase ( ) . includes ( 'set up vpn' ) ) {
234- console . log ( ' VPN dialog appeared after skip, continuing...' ) ;
235232 continue ;
236233 }
237234
238- if ( afterSkipXml . toLowerCase ( ) . includes ( 'disconnected' ) ) {
239- console . log ( ' App returned to disconnected state after skip' ) ;
235+ await delay ( 1000 ) ;
236+ if ( await checkVpnActive ( ) ) {
237+ return true ;
240238 }
241239 continue ;
242240 }
243241
244- if ( await tapText ( 'Cancel' , true ) ) {
245- console . log ( ' Tapped Cancel on manual setup dialog' ) ;
246- await delay ( 1000 ) ;
247- return false ;
248- }
242+ await delay ( 500 ) ;
243+ continue ;
249244 }
250245
251246 if ( isVpnDialog ) {
252- console . log ( ` Found VPN dialog (package: ${ currentPackage } ), looking for button...` ) ;
253-
254247 const positiveButtons = [ 'OK' , 'ALLOW' , 'Allow' , 'Connect' , 'CONNECT' , 'Yes' , 'YES' , 'I trust this app' ] ;
255248
256249 for ( const buttonText of positiveButtons ) {
257- const tapped = await tapText ( buttonText , true ) ;
258- if ( tapped ) {
259- console . log ( ` Tapped " ${ buttonText } " button` ) ;
250+ const button = findTextInXml ( xml , buttonText , true ) ;
251+ if ( button ) {
252+ await tap ( button . x , button . y ) ;
260253 await delay ( 1000 ) ;
261254 return true ;
262255 }
@@ -269,50 +262,39 @@ export async function handleVpnPermissionDialog(timeoutMs: number = 15000): Prom
269262 ] ;
270263
271264 for ( const buttonId of buttonIds ) {
272- const tapped = await tapResourceId ( buttonId ) ;
273- if ( tapped ) {
274- console . log ( ` Tapped button with ID ${ buttonId } ` ) ;
265+ const pattern = new RegExp ( `resource-id="${ buttonId } "[^>]*bounds="\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]"` ) ;
266+ const match = xml . match ( pattern ) ;
267+ if ( match ) {
268+ const [ , x1 , y1 , x2 , y2 ] = match . map ( Number ) ;
269+ await tap ( Math . round ( ( x1 + x2 ) / 2 ) , Math . round ( ( y1 + y2 ) / 2 ) ) ;
275270 await delay ( 1000 ) ;
276271 return true ;
277272 }
278273 }
279-
280- console . log ( ' Could not find button to tap. UI contains:' ) ;
281- const buttonMatches = xml . match ( / t e x t = " [ ^ " ] + " / g) || [ ] ;
282- console . log ( ' Text elements:' , buttonMatches . slice ( 0 , 10 ) . join ( ', ' ) ) ;
283274 }
284275
285276 if ( xml . toLowerCase ( ) . includes ( 'set a screen lock' ) ||
286277 xml . toLowerCase ( ) . includes ( 'set screen lock' ) ||
287278 ( xml . toLowerCase ( ) . includes ( 'screen lock' ) && xml . toLowerCase ( ) . includes ( 'security' ) ) ) {
288- console . log ( ' Found "Set screen lock" dialog - need to set up screen lock via UI' ) ;
289-
290279 const lockSetUp = await setupScreenLockViaUi ( ) ;
291280 if ( lockSetUp ) {
292- console . log ( ' Screen lock set up, retrying VPN permission...' ) ;
293281 await pressBack ( ) ;
294282 await delay ( 1000 ) ;
295283 continue ;
296284 } else {
297- console . log ( ' Could not set up screen lock via UI' ) ;
298285 await pressBack ( ) ;
299286 await delay ( 500 ) ;
300287 return false ;
301288 }
302289 }
303290
304291 if ( xml . toLowerCase ( ) . includes ( 'oh no' ) || xml . toLowerCase ( ) . includes ( "couldn't connect" ) ) {
305- console . log ( ' App showing error state' ) ;
306292 return false ;
307293 }
308294
309295 await delay ( 500 ) ;
310296 }
311297
312- console . log ( ' VPN dialog timeout. Last UI state had texts:' ) ;
313- const textMatches = lastXml . match ( / t e x t = " [ ^ " ] + " / g) || [ ] ;
314- console . log ( ' ' , textMatches . slice ( 0 , 15 ) . join ( ', ' ) ) ;
315-
316298 return false ;
317299}
318300
0 commit comments