> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mobster.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# System dialogs and languages

> Answer iOS's own alerts and sheets without a model, and run every check in each language.

iOS puts its own screens over your app: a location alert, Allow Paste, the Sign in with Apple sheet, a Face ID prompt, the purchase sheet. They move with every iOS release and they aren't yours. A check names each answer once in `dialogs`, and every run answers them without a model.

## Answer iOS dialogs

Add `dialogs` to an existing [check file](https://docs.mobster.dev/checks.md#the-check-file):

```yaml theme={"theme":"css-variables"}
dialogs:
  permissions: allow
  paste: allow
  sign_in_with_apple: continue
  biometric: match
  purchase: confirm
```

| Key | Answers | Default on a simulator |
| - | - | - |
| `permissions` | `allow`, `deny` or `ask` for Location, Photos, camera, microphone, contacts, calendar, notifications, tracking, "Open in app?" and the other permission alerts. `allow` presses the narrowest grant on offer (`Allow Once` before `Allow While Using App`), `deny` presses `Don't Allow` | `allow` |
| `paste` | `allow`, `deny` or `ask` for Allow Paste | `allow` |
| `sign_in_with_apple` | `continue` or `cancel` for the Sign in with Apple sheet | `continue` |
| `biometric` | A Face ID or Touch ID prompt. `match` and `nonmatch` send that event to the simulator, written to the run's action journal before it goes, `cancel` presses Cancel | `match` |
| `purchase` | The purchase sheet and the "all set" alert after it, which StoreKit marks `[Environment: Sandbox]` or `[Environment: Xcode]`. `confirm` presses `Buy` or `Subscribe`, `cancel` presses `Cancel`. On a simulator the all-set alert gets `OK` either way, since the purchase already went through | `confirm` |

Model steps, a replay and a launch-only check all use these answers. To test that the app handles a refusal, answer the other way:

```yaml theme={"theme":"css-variables"}
dialogs: { permissions: deny, purchase: cancel }
```

`ask` keeps the rule a task uses, and a check has nobody to ask, so the run stops and says which dialog iOS showed.

## Know which dialogs are never answered

iOS asking for your Apple Account password, a dialog with a password field, a Save Password prompt, Trust This Computer and an Apple Account sign-in, including one that offers Face ID or Touch ID, are never answered for you, whatever `dialogs` says. Mobster knows them by Apple's wording, so an app's own alert such as "Incorrect password" or "You're all set" stays the app's. An app's own alert that uses the same wording as one of these, such as "Enter your password to continue", or on your iPhone "Do you want to buy 100 coins?", stops the check instead of being answered.

Only iOS's own dialogs are answered, on a simulator and on your iPhone:

* An alert is answered only while WebDriverAgent reports SpringBoard in front, or the Apple process for its own kind: StoreKit for a purchase alert, AuthKit for a Sign in with Apple alert, CoreAuthUI for a Face ID alert. None of its lines may be on the app's own screen, so the app's alert stays the app's even when it says "location" or has Allow and Don't Allow buttons.
* A sheet is answered only while the one Apple process that shows it is in front and the screen Mobster reads is that process's own. The app's screen behind a real sheet is never taken for the sheet.
* A web page in an in-app browser or a web sign-in runs in Safari's process, so its `confirm()` and its look-alike Continue, Subscribe or "Log in with Face ID" are never answered.
* When WebDriverAgent can't say what is in front, nothing is pressed.

Each dialog Mobster answers is its own `system_dialog` event in `events.jsonl`: the kind, the button, and that the check chose it. A replay answers one between steps, so it doesn't count as a step or a heal, and a replay that meets a dialog it hasn't seen before answers it from `dialogs` instead of failing. A replay that meets one `dialogs` has no answer for ends `couldnt_run`, class `system_dialog`, and keeps its recorded steps. Within one run Mobster never answers the same dialog twice in a row: when the dialog it answered last is still there, or back, the run ends `couldnt_run`, class `system_dialog`. Mobster reads a dialog from WebDriverAgent's alert, or from the screen for a sheet an app presents. It never asks a model to read one.

## Handle a dialog at launch

A launch-only check has no steps. It watches for 2 seconds from the moment the app is in front. In that time it answers each iOS dialog, such as a notifications alert, with the answer `dialogs` names, or on a simulator with the default above. Then it reads the screen for its verdict. On your iPhone, a permission alert stays up when `dialogs` doesn't name `permissions`. A dialog that comes up after the 2 seconds stays up too. The failure names a dialog that stays up:

```text theme={"theme":"css-variables"}
✗ failed  YourApp opens on iPhone 17 Pro, iOS 26.4
    YourApp wasn't in front at the end: com.apple.springboard was; an alert says "“YourApp” Would Like to Send You Notifications".
```

A dialog that `dialogs` stops at in those 2 seconds, such as a camera alert under `permissions: ask`, ends the check `couldnt_run`, class `system_dialog`.

### What a simulator may do that an iPhone may not

| | Simulator | Your iPhone |
| - | - | - |
| Buy, Send, Post and Delete in the app under test | Approved. A commit Mobster can't classify is refused | Refused, and so is everything else [Ask before acting](https://docs.mobster.dev/safety.md#when-mobster-asks) asks about, such as Like, Block or a PIN ([How Mobster tells](https://docs.mobster.dev/safety.md#how-mobster-tells) lists more). Mobster asks nobody |
| Dialogs when `dialogs` is silent | The defaults above | Declines what it can (`Don't Allow Paste`, `Not Now`, `Later`) and stops at the rest. A permission alert is left to the step |
| `permissions: allow` | Presses the narrowest grant, such as `Allow Once` | Presses it too, so the app under test gets that access on your iPhone. Only while SpringBoard shows the alert, and `Open` in another app only for a link Mobster opened |
| `sign_in_with_apple: continue` | Presses `Continue` | Presses `Continue` too, which signs in to the app under test with your iPhone's Apple Account. Only while AuthKit's sheet is in front |
| `purchase: confirm` | Presses `Buy` | Never. Mobster stops at the sheet. `purchase: cancel` presses `Cancel` |
| `biometric: match` or `nonmatch` | Sends the event | Can't send one to a phone: it stops. `cancel` presses `Cancel` |
| `paste: allow` | Presses `Allow Paste` | Only for text Mobster put on the clipboard itself |
| `conditions.biometric` | Enrolls or unenrolls Face ID, and puts it back | Skipped, and the report says so |

On a simulator there is no money to spend and nobody to message, so the checks that matter most (the paywall buys, the composer sends) run. The app's own backend still receives what the app sends, so point a check at staging.

## Test in other languages

```sh theme={"theme":"css-variables"}
mobster test --locale de,ja,ar
```

Every check runs once in each language. `--locale` takes language codes, with a region if you like (`pt_BR`), separated by commas or repeated, at most 12. The run for `de` is the check "Home \[de]": it launches your app with `-AppleLanguages (de) -AppleLocale de`, the same as `conditions: {locale: de}` in a check file, and it keeps any other conditions the check sets. Mobster changes the app's language and region only, not the simulator's system settings. The same flag works with `--sim` and `--device`: a real iPhone gets the launch arguments and nothing else. `--locale` replaces a `locale` list in a check.

Each language is its own check in `results.json` (`locale` on the check), in `junit.xml` (the classname `checks.home.de`) and in the history, so a layout that breaks only in German has its own pass rate. Each language also has its own stable case ID, so saved-suite comparisons keep German and Japanese results apart.

The audit reads the screens each run already reads, with no model, and reports warnings that never fail the check:

| finding | what Mobster saw |
| - | - |
| `text-truncated` | A label that ends in `…` or `...`, or text whose frame runs past the screen's side edges or the container it sits in |
| `untranslated-string` | In a language that isn't written in Latin script (Japanese, Arabic, Hebrew, Russian, Korean, Chinese, Thai, Hindi and others), a label of plain English letters |
| `rtl-not-mirrored` | In a right-to-left language (Arabic, Hebrew, Persian, Urdu), a navigation bar with every button in its left half, or text in the language's own script flush to the left of a full-width row |

Right-to-left layout comes from iOS: with `ar`, the app mirrors itself when it uses leading and trailing edges, and `rtl-not-mirrored` tells you where it didn't.

The accessibility tree has labels and frames, not the rendered glyphs, so text cut off inside a frame of the right size isn't flagged. A brand name in Latin letters is flagged as untranslated in Japanese, which is why each finding is a warning. Languages written in Latin script (German, French, Spanish) get the truncation rule only.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.