Random Face-ID permission popup is completely freezing my iOS mobile test execution

Hey everyone,

I am completely stuck and losing my mind here. I am trying to automate our iOS mobile app using Katalon Studio, and everything works fine until it doesn’t.

When the app launches, iOS randomly throws that standard system permission popup asking for “Face ID Permission” (Allow or Don’t Allow). The problem is, it doesn’t happen every single time—only sometimes when the app updates or resets.

When it does appear, my entire test execution just freezes. Katalon tries to find the next regular app element, fails, waits for a long timeout, and then crashes the whole test suite.

Here is what I’ve tried so far:

  1. I tried using Mobile.tap() on the “Allow” button, but Katalon says it can’t find the element because it’s an OS-level alert, not part of my app’s object repository.

  2. I tried adding a Mobile.delay(5) to see if I could catch it manually, but since it’s random, adding hardcoded delays everywhere is ruining my execution times.

  3. I looked into the regular Mobile.acceptAlert(), but it just throws an error saying no alert is present, probably because it’s a system biometric prompt and not a standard web/app alert.

How do I make Katalon smart enough to handle this OS alert whenever it pops up, without breaking the script when it doesn’t? Please help!

In mobile automation (especially iOS with XCUITest), system-level alerts like Face ID, Location, or Notification permissions exist outside the application’s native view hierarchy. When these alerts appear, they steal the focus. If your automation script blindly attempts to interact with application elements, the driver will block or time out.

Handling this requires a reactive abstraction layer. Instead of scattering try-catch blocks across your test cases, the industry-standard approach is to leverage Appium’s background capabilities or build an automated Custom Keyword Guard that handles the alert condition dynamically before proceeding with the test workflow.

The Solution: Architecture Setup

  1. Desired Capabilities (The Preventive Layer): Ensure your Project Settings (Project > Settings > Desired Capabilities > Mobile > iOS) include autoAcceptAlerts = true. However, since Face ID prompts sometimes require a specific bypass or direct interaction, a programmatic guard is required.

  2. The Custom Keyword Guard (The Reactive Layer): We will create a robust Custom Keyword that checks for the presence of the system alert and handles it instantly using standard iOS XCUI element locators.

Here is the production-ready Custom Keyword to handle this cleanly.

Implementation Code

Create a new Custom Keyword (e.g., com.architecture.MobileAlertHandler) and paste the following implementation:

Groovy

package com.architecture

import com.kms.katalon.core.annotation.Keyword
import com.kms.katalon.core.mobile.keyword.internal.MobileDriverFactory
import io.appium.java_client.AppiumDriver
import org.openqa.selenium.By
import org.openqa.selenium.WebElement
import com.kms.katalon.core.util.KeywordUtil

public class MobileAlertHandler {

    /**
     * Checks for the iOS Face ID / System permission pop-up and handles it safely.
     * This will NOT fail your test if the pop-up is not present.
     */
    @Keyword
    def handleFaceIDPermissionIfPresent(String buttonText = "Allow") {
        try {
            AppiumDriver driver = MobileDriverFactory.getDriver()
            
            // iOS system alerts typically use standard accessibility identifiers or text attributes
            // We use a dynamic XPath looking for common iOS system button labels like "Allow" or "OK"
            String xpathExpression = String.format("//XCUIElementTypeButton[@name='%s']", buttonText)
            
            List<WebElement> elements = driver.findElements(By.xpath(xpathExpression))
            
            if (!elements.isEmpty()) {
                KeywordUtil.logInfo("System alert detected. Attempting to click: " + buttonText)
                elements.get(0).click()
                KeywordUtil.markPassed("Successfully dismissed the system permission alert.")
            } else {
                KeywordUtil.logInfo("No system permission alert detected. Proceeding normally.")
            }
        } catch (Exception e) {
            KeywordUtil.logWarning("An exception occurred while checking for system alerts: " + e.getMessage())
        }
    }
}

How to Use It in Your Test Case

Call this keyword immediately after your Mobile.startExistingApplication() or Mobile.startApplication() step:

Groovy

import com.kms.katalon.core.mobile.keyword.MobileBuiltInKeywords as Mobile
import CustomKeywords

// 1. Launch your mobile application
Mobile.startApplication('/path/to/your/app.ipa', false)

// 2. Call the custom keyword to intercept the random Face ID prompt safely
CustomKeywords.'com.architecture.MobileAlertHandler.handleFaceIDPermissionIfPresent'("Allow")

// 3. Continue your regular test steps smoothly
Mobile.tap(findTestObject('Object Repository/Main/Btn_Login'), 10)

The Face ID permission dialog is an iOS system alert, so it is outside your application’s UI hierarchy. That’s why:

  • Mobile.tap() on an Object Repository element won’t work.
  • Adding Mobile.delay() only slows down every execution and still doesn’t solve the random nature of the popup.
  • Mobile.acceptAlert() may not always work depending on the type of biometric/system prompt and the iOS/Appium versions being used.

SImple solution → if f you don’t need to validate the permission dialog itself, let Appium handle it automatically.

In your desired capabilities, enable:

autoAcceptAlerts = true

or, if your test requires denying permissions,

autoDismissAlerts = true

This automatically accepts (or dismisses) most iOS system permission dialogs, including camera, notifications, Face ID, location, etc.,.

or If you need to decide whether to Allow or Don’t Allow, don’t immediately interact with it. First check whether it exists.
Put hte below lib at the top of the test case and add that code snippet, where it is needed

Example:

import com.kms.katalon.core.mobile.keyword.MobileBuiltInKeywords as Mobile
import com.kms.katalon.core.model.FailureHandling

if (Mobile.waitForAlert(3, FailureHandling.OPTIONAL)) {
    Mobile.acceptAlert()
}

Using FailureHandling.OPTIONAL prevents the test from failing when the alert is not present.

no idea on this , never did it

can you try above solutions and let us know

hi @jfraney

i think it would be great if you could share the solution with us, since i’ve never implemented something like this before