@@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs';
22import { resolve , dirname } from 'node:path' ;
33import { fileURLToPath } from 'node:url' ;
44
5- import { byTestId , byText , getPlatform , getSdkType , getTestExternalId } from './selectors.js' ;
5+ import { byTestId , byText , getPlatform , getTestExternalId } from './selectors.js' ;
66
77const __dirname = dirname ( fileURLToPath ( import . meta. url ) ) ;
88const tooltipContent = JSON . parse (
@@ -128,13 +128,24 @@ async function clickFirstExisting(selectors: string[], timeout = 1500) {
128128 return false ;
129129}
130130
131+ /**
132+ * System permission dialogs live under SpringBoard on iOS, so treat them like
133+ * regular UI and click the expected button if it is visible.
134+ */
135+ async function clickIosSystemAlertButton ( buttonLabel : string ) {
136+ await driver . updateSettings ( { defaultActiveApplication : 'com.apple.springboard' } ) ;
137+ try {
138+ return await clickFirstExisting ( [
139+ `-ios class chain:**/XCUIElementTypeButton[\`label == "${ buttonLabel } "\`]` ,
140+ ] ) ;
141+ } finally {
142+ await driver . updateSettings ( { defaultActiveApplication : 'auto' } ) ;
143+ }
144+ }
145+
131146export async function allowNotifications ( ) {
132147 if ( driver . isIOS ) {
133- await driver . updateSettings ( {
134- acceptAlertButtonSelector : '**/XCUIElementTypeButton[`label == "Allow"`]' ,
135- } ) ;
136- await driver . acceptAlert ( ) ;
137- return true ;
148+ return clickIosSystemAlertButton ( 'Allow' ) ;
138149 }
139150
140151 return clickFirstExisting ( [
@@ -146,11 +157,7 @@ export async function allowNotifications() {
146157
147158export async function allowLocation ( ) {
148159 if ( driver . isIOS ) {
149- await driver . updateSettings ( {
150- acceptAlertButtonSelector : '**/XCUIElementTypeButton[`label == "Allow While Using App"`]' ,
151- } ) ;
152- await driver . acceptAlert ( ) ;
153- return true ;
160+ return clickIosSystemAlertButton ( 'Allow While Using App' ) ;
154161 }
155162
156163 return clickFirstExisting ( [
@@ -165,9 +172,9 @@ export async function allowLocation() {
165172/**
166173 * Wait for the app to fully launch and the home screen to be visible.
167174 *
168- * System permission dialogs are handled automatically by Appium via the
169- * `autoGrantPermissions` (Android) and `autoAcceptAlerts` (iOS) capabilities,
170- * so no explicit dialog handling is needed here .
175+ * Accepts the notification permission dialog if present. Safe to call multiple
176+ * times: on iOS the prompt only appears on first launch after install, and
177+ * `allowNotifications` no-ops when no alert is showing .
171178 */
172179export async function waitForAppReady ( opts : { skipLogin ?: boolean } = { } ) {
173180 const { skipLogin = false } = opts ;
0 commit comments