@@ -160,15 +160,20 @@ export async function acceptSystemAlert(timeoutMs = 10_000): Promise<string | nu
160160 }
161161
162162 // Try resource-id first (most robust across OEMs), then fall back to
163- // text match. "Don't allow" contains "Allow" as a substring, so we
164- // must use exact `text()` rather than `textContains()`.
163+ // clickable text/description. "Don't allow" contains "Allow" as a
164+ // substring, so we must use exact match rather than `textContains()`.
165+ // Samsung One UI wraps the label in a clickable container, so we also
166+ // target the clickable ancestor by text or content-description.
165167 const allowSelectors = [
166168 'new UiSelector().resourceIdMatches(".*:id/permission_allow_button")' ,
167169 'new UiSelector().resourceIdMatches(".*:id/permission_allow_foreground_only_button")' ,
168170 'new UiSelector().resourceIdMatches(".*:id/permission_allow_one_time_button")' ,
169- 'new UiSelector().text("Allow")' ,
170- 'new UiSelector().text("ALLOW")' ,
171- 'new UiSelector().text("While using the app")' ,
171+ 'new UiSelector().clickable(true).textMatches("(?i)^allow$")' ,
172+ 'new UiSelector().clickable(true).descriptionMatches("(?i)^allow$")' ,
173+ 'new UiSelector().clickable(true).textMatches("(?i)^while using the app$")' ,
174+ 'new UiSelector().textMatches("(?i)^allow$")' ,
175+ 'new UiSelector().descriptionMatches("(?i)^allow$")' ,
176+ 'new UiSelector().textMatches("(?i)^while using the app$")' ,
172177 ] ;
173178
174179 let allowBtn : Awaited < ReturnType < typeof $ > > | null = null ;
@@ -198,6 +203,17 @@ export async function acceptSystemAlert(timeoutMs = 10_000): Promise<string | nu
198203 } catch {
199204 /* best-effort */
200205 }
206+ // If we matched a non-clickable text node (common on Samsung where the
207+ // label sits inside a clickable container), tap the nearest clickable
208+ // ancestor so the button actually fires.
209+ if ( ( await allowBtn . getAttribute ( 'clickable' ) . catch ( ( ) => 'true' ) ) !== 'true' ) {
210+ const clickableAncestor = await $ (
211+ '//*[@clickable="true" and .//*[(@text="Allow" or @content-desc="Allow")]]' ,
212+ ) ;
213+ if ( await clickableAncestor . isDisplayed ( ) . catch ( ( ) => false ) ) {
214+ allowBtn = clickableAncestor ;
215+ }
216+ }
201217 await allowBtn . click ( ) ;
202218 return text ;
203219 } catch {
@@ -224,7 +240,7 @@ export async function waitForAppReady(opts: { skipLogin?: boolean } = {}) {
224240 const alertHandled = await browser . sharedStore . get ( 'alertHandled' ) ;
225241 if ( ! alertHandled ) {
226242 // Accept permission dialogs until the app UI is visible.
227- await acceptSystemAlerts ( 5_000 ) ;
243+ await acceptSystemAlerts ( 10_000 ) ;
228244 await browser . sharedStore . set ( 'alertHandled' , true ) ;
229245 }
230246
0 commit comments