Pickers, sheets and system alerts
For AI agents: this page is Markdown at https://squiggle.sh/docs/flows/system-ui.md. Every docs page is listed at https://squiggle.sh/llms.txt, and all of them are one file at https://squiggle.sh/llms-full.txt.
A flow can drive the photo picker, the document picker, the share sheet and permission prompts that iOS shows over your app. Select their controls by the text they show, like any other control.
These run on the iOS simulator. iOS draws them in a separate process, on top of your app, and Squiggle reads them from the simulator’s accessibility tree. Your flow doesn’t need a mock or a test-only code path to get past an attachment step.
What a flow can drive
| UI | Examples |
|---|---|
Sheets and pickers shown over your app | The photo picker (one photo or several), the document picker and the share sheet |
System alerts | Permission prompts, such as photo library access |
While one of these is up, your app’s own screen is covered. Squiggle stops listing the controls beneath it and refuses to tap them, because a real finger would land on the sheet instead. Agents see the same thing: describe marks each control on the sheet external.
Attach photos from the picker
Open the picker, wait for it, tap photos and confirm. The wait names the picker’s Collections tab, a label nothing in the app contains (see Wait for it to open). Every cell in the photo picker is labelled Photo, so nth picks one by position:
- tap: { testID: attach-photos }- waitFor: { visible: { text: Collections } }- tap: { text: Photo, nth: 2 }- tap: { text: Photo, nth: 4 }- assert: { visible: { text: Show Selected (2) } }- tap: { text: Add }- assert: { visible: { testID: attachment-count, text: Attachments 2 } }nth counts from 1 in reading order: along the top row from left to right, then the next row. A bare text: Photo taps the first cell. For a single-photo picker, the tap on a photo closes the picker and returns it to your app.
The picker shows the simulator’s photo library. A new simulator has a few sample photos. To attach a particular image, add it before the run:
xcrun simctl addmedia booted ./fixtures/receipt.jpgAttach a file from the document picker
The document picker opens on Recents, or in the folder it was last closed in. Tap the Browse tab to start from the list of locations, open one, and tap the file by its name:
- tap: { testID: attach-file }- waitFor: { visible: { text: Browse } }- tap: { text: Browse }- tap: { text: On My iPhone }- waitFor: { visible: { text: invoice-1042 } }- tap: { text: invoice-1042 }- assert: { visible: { text: invoice-1042.txt } }A file’s label is its name without the extension, followed by its kind, date and size (invoice-1042, Text file, 12:00, 30 bytes), so text: invoice-1042 matches it. Your app then sees the whole file name.
On My iPhone starts empty on a simulator. It is a folder on your Mac, so copy the files your flow needs into it before the run:
DIR=$(xcrun simctl get_app_container booted com.apple.DocumentsApp groups | awk -F'\t' '/FileProvider.LocalStorage/ {print $2}')cp ./fixtures/invoice-1042.txt "$DIR/File Provider Storage/"Wait for it to open
A sheet appears a moment after the tap that opens it. Wait for one of its controls with waitFor: { visible: … }, which checks again until the control shows or the timeout passes (8 seconds unless you set timeout). Use notVisible to wait for something to go away.
Pick a label that no text on your own screen contains. Until the sheet covers your screen, text can also match part of a label in your app: text: Photo matches an Attach photo button and text: Cancel matches Upload cancelled, so the wait passes before the sheet opens.
A tap on the sheet, such as Add or Close, waits for the screen to stop moving before it touches the sheet, because iOS ignores a touch on a sheet or alert that is still sliding in. It waits again afterwards, so the next step can check your app straight away.
Closing the share sheet:
- tap: { testID: share-button }- waitFor: { visible: { text: Copy } }- tap: { text: Close }Answer a permission prompt
Tap the button by its text. Your app is inactive while the prompt is up, so a step that names one of its controls fails until the prompt is answered.
- tap: { testID: ask-photos }- waitFor: { visible: { text: Allow Full Access } }- tap: { text: Allow Full Access }iOS asks for each permission once. To see the prompt again on the next run, reset the permission first:
xcrun simctl privacy booted reset photos com.example.appLimits
- These controls have no
testID. Select them by their text (text:), which is in the simulator’s language. Run flows in one language, or keep one flow per language. - Text is the label iOS gives the control, which can differ from what you see. The photo picker’s cells read
Photo, not the file name. To see the labels, open the sheet and ask your agent todescribethe screen. - A flow can’t continue in another app your app opens, such as Settings or Safari: iOS suspends your app while it is in the background.
- On a macOS app, only the app’s own windows can be driven.