Resource library

QA How-To

Appium 3 Biometric Authentication Testing Tutorial (2026)

Follow this Appium 3 biometric authentication testing tutorial to automate fingerprint, Touch ID, Face ID, success, failure, and fallback flows in CI.

22 min read | 2,746 words

TL;DR

Run biometric UI tests on an Android Emulator or iOS Simulator. Trigger the native prompt in your app, inject a fingerprint or biometric match through an Appium execute method, then assert the app's authenticated or fallback state.

Key Takeaways

  • Use Android Emulator or iOS Simulator for deterministic biometric event injection.
  • Call mobile: fingerprint for Android and mobile: sendBiometricMatch for iOS.
  • Enroll biometrics before testing and trigger the app prompt before injecting a match.
  • Assert application state after each biometric event instead of treating command success as proof.
  • Cover success, rejection, retry, cancellation, lockout, and passcode fallback as separate behaviors.
  • Reset authentication and enrollment state so tests remain independent in local and CI runs.

This Appium 3 biometric authentication testing tutorial builds repeatable fingerprint, Touch ID, and Face ID checks with WebdriverIO and TypeScript. You will trigger the real operating-system prompt, inject success or failure on a simulator, and verify what your application does after the callback.

Biometric automation is not an image-recognition exercise. Appium asks the simulator to report an authentication result to the native biometric API. Your test must still prove that the app handles that result correctly, protects sensitive content, and offers a usable fallback.

The examples use a small authentication contract that you can map to your app's accessibility IDs. For broader framework setup, read the Appium 3 mobile automation complete guide.

TL;DR

Platform Appium driver Event command Supported target
Android UiAutomator2 7.5.2 mobile: fingerprint Android Emulator, API 23+
iOS XCUITest 11.7.3 mobile: sendBiometricMatch iOS Simulator
iOS enrollment XCUITest 11.7.3 mobile: enrollBiometric iOS Simulator

The reliable sequence is always the same: establish enrollment, launch a clean app state, tap the control that requests biometric authentication, wait for the native prompt, inject the result, and assert an app-level outcome. Android accepts a numeric fingerprint ID. iOS accepts touchId or faceId plus a Boolean match. These simulator commands do not reproduce the sensor hardware or provide a general way to inject biometrics on physical devices.

What You Will Build

You will create a TypeScript test project with two runnable suites. By the end, it will:

  • start an Appium 3 session against an Android Emulator or iOS Simulator;
  • prove that a successful biometric callback opens a protected account screen;
  • prove that a rejected biometric attempt leaves protected data inaccessible;
  • exercise retry and passcode fallback behavior without test-order dependencies;
  • use one platform adapter so business-flow tests do not contain operating-system branches;
  • capture useful state when the OS prompt or application callback behaves unexpectedly.

The sample application contract uses accessibility IDs use-biometrics, account-screen, authentication-error, try-again, and use-passcode. Replace those constants once with identifiers from your app. Accessibility IDs are preferable because they are readable and normally work on both platforms. The Appium locator strategies guide explains how to inspect and stabilize alternatives.

Prerequisites

Use the verified tutorial baseline: Node.js 22.12.0, npm 10.x, Appium 3.2.0, WebdriverIO 9.30.0, UiAutomator2 7.5.2, and XCUITest 11.7.3. Appium 3 itself requires Node.js 20.19 or newer, but Node 22.12.0 is a practical shared baseline for the current drivers and runner. Keep these patch versions in your lockfile, then upgrade deliberately after reviewing driver release notes.

For Android, install Android Studio with an Android 15 or 16 SDK, current platform-tools, and an x86_64 or arm64 Google APIs emulator image. Create an emulator running API 35 or 36. The fingerprint command requires API 23 or newer. Confirm that adb, emulator, and Java 17 are available on PATH.

For iOS, use macOS with Xcode 16 or newer and an installed iOS 18 or newer Simulator runtime. XCUITest cannot run from Windows or Linux because it depends on Xcode. The biometric injection methods in this guide target Simulator, not a physical iPhone.

Install the server and drivers:

npm install --global appium@3.2.0
appium driver install uiautomator2@7.5.2
appium driver install xcuitest@11.7.3
appium driver list --installed
appium driver doctor uiautomator2
appium driver doctor xcuitest

Create the test project:

mkdir appium-biometric-tests
cd appium-biometric-tests
npm init -y
npm install --save-dev webdriverio@9.30.0 typescript@5 @types/node@22

You also need a debug APK or Simulator .app whose biometric feature is enabled. Set ANDROID_APP to the absolute APK path or IOS_APP to the absolute .app directory. If driver installation is new to you, follow installing Appium 3 drivers and plugins before continuing.

Verification: Run node --version, appium --version, and appium driver list --installed. The first should be at least v22.12.0, the second should begin with 3, and the relevant platform driver should display as installed.

Step 1: Create the TypeScript Project Configuration

Add module settings and scripts before writing test code. This tutorial uses Node's native test runner, so the suite needs no separate Mocha service or Appium service wrapper. Appium runs as a visible process in another terminal, which makes server logs easy to inspect.

Create tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "noEmit": true,
    "types": ["node"]
  },
  "include": ["test/**/*.ts"]
}

Update the relevant part of package.json:

{
  "type": "module",
  "scripts": {
    "typecheck": "tsc --noEmit",
    "test:android": "node --import tsx --test test/android.biometric.test.ts",
    "test:ios": "node --import tsx --test test/ios.biometric.test.ts"
  },
  "devDependencies": {
    "@types/node": "^22.0.0",
    "tsx": "^4.0.0",
    "typescript": "^5.0.0",
    "webdriverio": "9.30.0"
  }
}

Install the added runtime transpiler with npm install. Node's test hooks will own session setup and cleanup, while assert will validate app behavior. This keeps the example small without changing any Appium command.

Verification: Run npm run typecheck. It should exit with code 0. Run npm test -- --help if you want to confirm the Node test runner is present; do not expect a device session yet.

Step 2: Start Appium and Prepare the Simulators

Start Appium from the project directory in its own terminal:

appium --address 127.0.0.1 --port 4723 --log-level info

Appium 3 uses / as the default server base path. Do not copy an old client configuration that points to /wd/hub unless you deliberately start the server with that base path. A healthy log includes the installed driver names and a listener on port 4723.

For Android, boot the named AVD and wait for Android to finish starting:

emulator -avd Pixel_9_API_35
adb wait-for-device
adb shell getprop sys.boot_completed

The last command should print 1. Open Settings once and configure a PIN plus at least one fingerprint if your application checks device enrollment before presenting its biometric switch. You can also use the emulator's Extended controls UI for an initial enrollment. The test command later submits fingerprint ID 1.

For iOS, list available devices and boot one:

xcrun simctl list devices available
xcrun simctl boot "iPhone 16 Pro"
open -a Simulator

Do not boot both platforms unless you intend to run both suites. Explicit device identifiers prevent Appium from attaching to an unintended simulator when several are active. The Android Appium setup guide and iOS Appium setup guide cover SDK signing and environment details.

Verification: Open http://127.0.0.1:4723/status or run curl http://127.0.0.1:4723/status. The JSON response should report that the server is ready. Confirm one target with adb devices or xcrun simctl list devices booted.

Step 3: Add Capabilities and a Shared Session Factory

Create test/session.ts. Environment variables keep app paths and device identifiers outside source control. noReset: false favors isolation for this tutorial; mature suites may replace it with targeted app-data cleanup.

import { remote, type Browser } from 'webdriverio';

export type MobileBrowser = Browser;

const server = {
  hostname: '127.0.0.1',
  port: 4723,
  path: '/',
  logLevel: 'info' as const,
};

export async function startAndroid(): Promise<MobileBrowser> {
  const app = process.env.ANDROID_APP;
  if (!app) throw new Error('Set ANDROID_APP to an absolute APK path');

  return remote({
    ...server,
    capabilities: {
      platformName: 'Android',
      'appium:automationName': 'UiAutomator2',
      'appium:udid': process.env.ANDROID_UDID ?? 'emulator-5554',
      'appium:app': app,
      'appium:noReset': false,
      'appium:newCommandTimeout': 120,
    },
  });
}

export async function startIOS(): Promise<MobileBrowser> {
  const app = process.env.IOS_APP;
  if (!app) throw new Error('Set IOS_APP to an absolute Simulator .app path');

  return remote({
    ...server,
    capabilities: {
      platformName: 'iOS',
      'appium:automationName': 'XCUITest',
      'appium:deviceName': process.env.IOS_DEVICE ?? 'iPhone 16 Pro',
      'appium:platformVersion': process.env.IOS_VERSION ?? '18.0',
      'appium:app': app,
      'appium:noReset': false,
      'appium:newCommandTimeout': 120,
    },
  });
}

Use udid for Android selection because deviceName does not uniquely select an Android device. For iOS CI, prefer a Simulator UDID over a display name when runners can contain duplicate devices. All vendor capabilities include the mandatory appium: prefix.

Verification: Temporarily call startAndroid() or startIOS() from a one-line smoke test, print await driver.getSession(), and call deleteSession() in finally. Appium should install the app and return a session object without an unknown-capability warning.

Step 4: Encapsulate Android and iOS Biometric Commands

Create test/biometrics.ts. A narrow adapter prevents platform-specific execute-method names from leaking into every scenario.

import type { MobileBrowser } from './session.js';

export type BiometricKind = 'touchId' | 'faceId';

export async function sendAndroidFingerprint(
  driver: MobileBrowser,
  fingerprintId = 1,
): Promise<void> {
  if (!Number.isInteger(fingerprintId) || fingerprintId < 1) {
    throw new Error('fingerprintId must be a positive integer');
  }
  await driver.execute('mobile: fingerprint', { fingerprintId });
}

export async function setIOSBiometricEnrollment(
  driver: MobileBrowser,
  isEnabled: boolean,
): Promise<void> {
  await driver.execute('mobile: enrollBiometric', { isEnabled });
}

export async function isIOSBiometricEnrolled(
  driver: MobileBrowser,
): Promise<boolean> {
  return driver.execute('mobile: isBiometricEnrolled');
}

export async function sendIOSBiometricResult(
  driver: MobileBrowser,
  type: BiometricKind,
  match: boolean,
): Promise<void> {
  await driver.execute('mobile: sendBiometricMatch', { type, match });
}

mobile: fingerprint only emulates a scan on Android Emulator API 23 or newer. Its numeric ID identifies a virtual finger, not a confidence score. XCUITest separates enrollment from matching. mobile: sendBiometricMatch accepts touchId or faceId and reports either a match or non-match to an active prompt.

Do not infer business success from a resolved execute promise. Resolution only proves that the driver accepted the simulator command. The app could ignore the callback, mishandle the error, or expose protected content too early. That is why the next steps assert visible application state.

Verification: Run npm run typecheck. Misspelling an execute method will not necessarily be caught by TypeScript because execute extensions are strings, so keep these reviewed names centralized.

Step 5: Appium 3 Biometric Authentication Testing Tutorial for Android

Create test/android.biometric.test.ts. The selectors describe the sample contract declared earlier. Replace their values, not the test logic.

import assert from 'node:assert/strict';
import { after, before, test } from 'node:test';
import type { MobileBrowser } from './session.js';
import { startAndroid } from './session.js';
import { sendAndroidFingerprint } from './biometrics.js';

let driver: MobileBrowser;
const byId = (id: string) => `~${id}`;

before(async () => { driver = await startAndroid(); });
after(async () => { if (driver) await driver.deleteSession(); });

test('valid fingerprint opens the protected account', async () => {
  await driver.$(byId('use-biometrics')).click();
  await driver.pause(500);
  await sendAndroidFingerprint(driver, 1);

  const account = driver.$(byId('account-screen'));
  await account.waitForDisplayed({ timeout: 5000 });
  assert.equal(await account.isDisplayed(), true);
});

test('unknown fingerprint keeps protected content hidden', async () => {
  await driver.reloadSession();
  await driver.$(byId('use-biometrics')).click();
  await driver.pause(500);
  await sendAndroidFingerprint(driver, 99);

  const error = driver.$(byId('authentication-error'));
  await error.waitForDisplayed({ timeout: 5000 });
  assert.equal(await driver.$(byId('account-screen')).isDisplayed(), false);
  assert.match(await error.getText(), /not recognized|try again/i);
});

The short pause is only a prompt-readiness buffer. Replace it with an app-owned indicator if your screen exposes one. Native Android biometric UI may not be part of the UiAutomator accessibility tree, so waiting for a stable application signal or using a small bounded delay can be more dependable than locating prompt text. Keep the delay below the app's authentication timeout.

Fingerprint 99 models a non-enrolled print only if your emulator considers that ID unknown. Confirm your emulator state first. A stricter negative suite can submit repeated unknown IDs and assert the app's lockout or fallback policy after the documented number of attempts.

Verification: Export ANDROID_APP=/absolute/path/app-debug.apk, then run npm run test:android. Expect two passing tests. During the run, the Android biometric prompt should appear briefly; success opens the account, while the unknown print produces the app error and never displays protected content.

Step 6: Appium 3 Biometric Authentication Testing Tutorial for iOS

Create test/ios.biometric.test.ts. Enroll Face ID through XCUITest before each scenario so the test does not depend on a manually changed Simulator menu.

import assert from 'node:assert/strict';
import { after, before, beforeEach, test } from 'node:test';
import type { MobileBrowser } from './session.js';
import { startIOS } from './session.js';
import {
  isIOSBiometricEnrolled,
  sendIOSBiometricResult,
  setIOSBiometricEnrollment,
} from './biometrics.js';

let driver: MobileBrowser;
const byId = (id: string) => `~${id}`;

before(async () => { driver = await startIOS(); });
after(async () => { if (driver) await driver.deleteSession(); });

beforeEach(async () => {
  await setIOSBiometricEnrollment(driver, true);
  assert.equal(await isIOSBiometricEnrolled(driver), true);
});

test('Face ID match opens the protected account', async () => {
  await driver.$(byId('use-biometrics')).click();
  await driver.pause(500);
  await sendIOSBiometricResult(driver, 'faceId', true);

  await driver.$(byId('account-screen')).waitForDisplayed({ timeout: 5000 });
  assert.equal(await driver.$(byId('account-screen')).isDisplayed(), true);
});

test('Face ID non-match shows a retry without exposing data', async () => {
  await driver.reloadSession();
  await setIOSBiometricEnrollment(driver, true);
  await driver.$(byId('use-biometrics')).click();
  await driver.pause(500);
  await sendIOSBiometricResult(driver, 'faceId', false);

  const retry = driver.$(byId('try-again'));
  await retry.waitForDisplayed({ timeout: 5000 });
  assert.equal(await retry.isDisplayed(), true);
  assert.equal(await driver.$(byId('account-screen')).isDisplayed(), false);
});

Use touchId instead of faceId when the chosen Simulator model and application flow use Touch ID. The command's type must agree with the scenario you intend to exercise. Enrollment should happen before the app creates its local authentication context, and the match should be sent only after tapping the biometric control.

A failed Face ID event may leave the system prompt active or make the app reveal a retry control, depending on your LAContext implementation and localized reason string. Assert the contract your product defines, not Apple's prompt wording.

Verification: Export IOS_APP=/absolute/path/MyApp.app, IOS_DEVICE='iPhone 16 Pro', and the installed runtime version in IOS_VERSION. Run npm run test:ios. Expect both cases to pass, with the protected view absent after the non-match.

Step 7: Test Cancellation, No Enrollment, and Passcode Fallback

Success and one rejection are insufficient for authentication risk. Add scenarios that prove a user is never trapped and that sensitive state remains closed. The exact cancellation gesture is platform-dependent, so prefer an app-owned Cancel control when product design provides one.

Add this iOS no-enrollment case before re-enabling enrollment for later cases:

test('no biometric enrollment offers passcode fallback', async () => {
  await driver.reloadSession();
  await setIOSBiometricEnrollment(driver, false);
  assert.equal(await isIOSBiometricEnrolled(driver), false);

  await driver.$(byId('use-biometrics')).click();
  const fallback = driver.$(byId('use-passcode'));
  await fallback.waitForDisplayed({ timeout: 5000 });

  assert.equal(await fallback.isDisplayed(), true);
  assert.equal(await driver.$(byId('account-screen')).isDisplayed(), false);
});

Add a retry case to either suite:

test('failed biometric can be retried successfully', async () => {
  await driver.reloadSession();
  await setIOSBiometricEnrollment(driver, true);
  await driver.$(byId('use-biometrics')).click();
  await driver.pause(500);
  await sendIOSBiometricResult(driver, 'faceId', false);

  await driver.$(byId('try-again')).click();
  await driver.pause(500);
  await sendIOSBiometricResult(driver, 'faceId', true);
  await driver.$(byId('account-screen')).waitForDisplayed({ timeout: 5000 });
});

Also cover backgrounding while the prompt is open, expiration of the authenticated session, and repeated-failure lockout if those behaviors exist in your threat model. Do not guess the lockout count. Read it from the product requirement because operating-system policy and app policy can differ. A fallback test should enter a non-production fixture passcode and assert the same authorization boundary as biometrics.

Verification: Run the iOS suite twice without changing Simulator menus. Both runs should begin from controlled enrollment state. The no-enrollment test must expose fallback, and the retry test must reach the account only after the positive event.

Step 8: Make Biometric Tests Stable in CI

Give each CI worker one simulator, one Appium port, and one unique system port. Avoid parallel tests inside a single device session because biometric prompts are global device UI. Parallelize across isolated emulators instead.

For Android, create or restore a known AVD snapshot, wait for sys.boot_completed, unlock the screen, and then start Appium. For iOS, create a fresh Simulator or erase a dedicated CI device between jobs. Fresh devices reduce hidden enrollment, keychain, and lockout state. They cost setup time, so a well-maintained snapshot is a reasonable Android compromise.

Use explicit waits for app-owned outcomes. A biometric event is asynchronous: the native framework calls your app, your app may exchange a token with a backend, and only then does navigation occur. Apply the patterns in Appium wait strategies, with one timeout covering the expected end state rather than multiple arbitrary sleeps.

On failure, save a screenshot and page source after dismissing or resolving the native prompt when possible. Also preserve the Appium server log, device log (adb logcat), or Simulator log. Mask tokens, account identifiers, and any passcode fixtures before publishing CI artifacts.

Maintain separate tags for simulator biometric checks and physical-device authentication checks. Real-device tests can validate prompt appearance, cancellation, and manual hardware interaction, but standard Appium commands cannot manufacture a real fingerprint or face. Cloud device providers may expose vendor-specific biometric APIs; isolate those behind the same adapter rather than mixing them into product-flow assertions.

Verification: Run the same suite three times on a clean CI-style simulator. All runs should pass without manual enrollment or menu actions, Appium should delete every session, and a forced assertion failure should upload enough artifacts to identify the screen and driver command that failed.

Troubleshooting

Problem: Unknown mobile command fingerprint -> Confirm the session uses UiAutomator2, not a browser or another Android driver. Run appium driver list --installed, update the driver, and verify that the target is an Android Emulator on API 23 or newer. The method name must be exactly mobile: fingerprint, with { fingerprintId: 1 }.

Problem: Android accepts the execute command but the prompt does not change -> Send the fingerprint only after the application has requested biometric authentication. Verify that a screen lock and fingerprint are enrolled, the emulator window is focused, and the chosen ID is enrolled for a success case. Inspect adb logcat for BiometricPrompt or application callback errors.

Problem: mobile: sendBiometricMatch says biometrics are not enrolled -> Call mobile: enrollBiometric with { isEnabled: true } before opening the prompt, then assert mobile: isBiometricEnrolled returns true. This API is for iOS Simulator; it is not a workaround for physical-device security.

Problem: iOS match is sent but the test times out -> Check that type matches the Simulator and flow, either faceId or touchId. Trigger the app prompt first, then inject the event. Assert your app's accessibility identifier rather than localized Face ID prompt text, and inspect the Simulator plus Appium logs for an LAContext error.

Problem: Session creation fails with /wd/hub or 404 -> Appium 3's normal base path is /. Set the WebdriverIO path to /, or deliberately configure the Appium server with a legacy base path and keep both sides consistent.

Problem: Tests pass alone but fail in the full suite -> Enrollment, lockout, keychain, and logged-in app state are leaking between cases. Reset enrollment explicitly, log out through a supported test hook, recreate the session, and avoid concurrent biometric tests on one simulator. Assign a unique device and Appium-related ports to each parallel worker.

Interview Questions and Answers

The model answers in the interviewQnA section below cover the distinctions interviewers usually probe: simulator versus device scope, enrollment versus matching, application assertions, negative paths, and CI isolation. A strong practical answer names both platform commands and explains why command completion is not the final assertion.

Best Practices

  • Keep biometric execute methods behind a typed platform adapter. A driver upgrade then has one review point.
  • Trigger the system prompt through the user interface. Calling an app test hook that skips BiometricPrompt or LAContext does not validate integration.
  • Assert authorization boundaries, not only toast text. Protected data must remain absent after rejection, cancellation, timeout, or no enrollment.
  • Keep test accounts and fallback secrets synthetic. Never store real user credentials, biometric-derived material, or production recovery codes in fixtures.
  • Give each scenario a known enrollment and login state. Test order should not determine whether a fingerprint is recognized.
  • Separate simulator injection coverage from real-device usability coverage. The former is deterministic; the latter validates hardware, permissions, accessibility, and genuine user interaction.
  • Avoid brittle selectors inside OS-owned dialogs. Prefer your app's accessible trigger and post-callback state.
  • Record driver, platform, runtime, and app build versions in CI artifacts. Biometric behavior often changes at an OS or driver boundary.

Where To Go Next

Turn the examples into page objects, move device capabilities into environment-specific configuration, and add the cancellation, lockout, app-background, session-expiry, and fallback cases required by your threat model. Then place the suite on isolated simulator workers and review its logs for sensitive information.

Continue with Appium 3 config file setup to manage environments, Appium parallel testing to allocate ports and devices, and Appium 3 driver version management to make upgrades repeatable. You can also use the QA practice workspace to rehearse scenario design.

Conclusion

A dependable Appium 3 biometric suite controls simulator enrollment, opens the real native prompt, injects the correct platform event, and verifies the application's authorization result. Android Emulator uses mobile: fingerprint; iOS Simulator uses enrollment methods plus mobile: sendBiometricMatch. Neither command substitutes for real-hardware validation.

Start with one positive and one rejection case on each supported platform. Add no-enrollment and fallback behavior next, then isolate the devices in CI. That small sequence gives you meaningful security and regression coverage without coupling the suite to unstable operating-system dialog locators.

Interview Questions and Answers

How does Appium automate biometric authentication on Android and iOS?

Appium delegates to simulator-specific driver extensions. UiAutomator2 exposes `mobile: fingerprint` for Android Emulator, while XCUITest exposes `mobile: enrollBiometric`, `mobile: isBiometricEnrolled`, and `mobile: sendBiometricMatch` for iOS Simulator. The test triggers the native prompt, injects an event, and then asserts the app's authorization state.

What is the difference between biometric enrollment and biometric matching?

Enrollment represents whether the simulated device has a biometric identity configured. Matching is the success or failure event delivered to an active authentication request. On iOS these are separate Appium commands, so a stable test controls enrollment before opening the prompt and sends a match only afterward.

Why is a successful execute command not enough to pass a biometric test?

It only proves that the driver accepted the simulator instruction. The app may mishandle the callback, navigate incorrectly, retain stale authorization, or reveal data before authentication. A meaningful assertion checks the protected app state and, for negative cases, proves that restricted content remains absent.

How would you reduce flakiness in biometric automation?

I would control enrollment and app state per test, wait for an app-owned prompt-ready or post-authentication signal, and isolate each parallel worker on its own simulator. I would centralize platform commands, avoid OS-dialog text locators, and preserve Appium plus device logs on failure. Fixed sleeps would be limited to a short, bounded prompt-readiness buffer when no observable signal exists.

What biometric scenarios belong in a security-focused regression suite?

The suite should include success, non-match, cancellation, no enrollment, retry, fallback, repeated failure, lockout, backgrounding, and session expiration as applicable. Every rejection path should assert that protected data and actions are unavailable. I would also test fallback credentials independently and verify that logs and screenshots do not leak secrets.

Can the same biometric test run on simulators and real phones?

The user-flow assertions can be shared, but standard event injection cannot. Appium's Android fingerprint and iOS biometric-match methods are simulator capabilities. I would keep a common business-flow layer, provide separate simulator and device adapters, and use manual or approved provider-specific hardware interaction for real phones.

Frequently Asked Questions

Can Appium 3 test fingerprint authentication on Android?

Yes. With a UiAutomator2 session on an Android Emulator running API 23 or newer, execute `mobile: fingerprint` with a numeric `fingerprintId`. Trigger the app's biometric prompt first, then assert the application's authenticated or rejected state.

How do I simulate Face ID success with Appium?

On an iOS Simulator using XCUITest, enroll biometrics with `mobile: enrollBiometric`, open the Face ID prompt, and execute `mobile: sendBiometricMatch` with `{ type: 'faceId', match: true }`. Verify a protected app screen rather than assuming the command itself proves login.

Can Appium inject biometric results on real devices?

The standard Android fingerprint and iOS biometric-match commands in this tutorial are simulator features. Physical devices protect sensor input, so use manual interaction or a device-cloud-specific facility for hardware coverage. Keep vendor extensions behind a platform adapter.

Why does mobile: sendBiometricMatch fail before the prompt appears?

The simulator needs an active biometric request to receive the event. Enroll biometrics first, tap the application control that creates the native authentication context, wait until the request is active, and only then send the match or non-match.

Should biometric tests locate the Android or iOS system dialog?

Usually no. OS dialogs can be outside the normal accessibility tree and their labels vary by runtime and locale. Interact with an app-owned trigger, inject the simulator event, and wait for an app-owned success, error, retry, or fallback element.

Which negative biometric scenarios should I automate?

Cover a non-match, cancellation, no enrollment, retry, fallback, repeated-failure lockout, app backgrounding, and session expiry when those paths exist. After every negative result, explicitly prove that protected content and privileged actions remain unavailable.

Related Guides