
How I QA iOS Apps
When I started designing, building, and testing iOS apps, I over-indexed on the happy path. Which would be fine if everyone used your app exactly as intended, with your exact phone model and your exact system settings and your exact same physical/cognitive abilities. However, scientists have recently discovered that humans are different. And they will use your software in novel, inane ways. As the designer/developer, it’s your responsibility to save them from themselves.
I’ve spent a lot of time on small teams where I was the only person checking for visual quality assurance (VQA) and general bugs. As such, I’ve developed a Sixth Sense (1999) for where and how apps tend to break. Cole Sear sees dead people, I see race conditions. They’re equally scary. Since a rising tide lifts all boats, I’m sharing the checklist I run through in my mind when testing apps and filing bugs.
The long and short of my list is to interrupt everything. Rotate, background, swipe, tap too fast, switch modes, change accessibility settings, toggle obscure preferences. Be the chaos monkey. Even dip into adversarial testing — if you were trying to break this app, how would you go about it? If you spot some common bug, what can you do in the app to make it even worse? E.g. if you notice a certain list view causes the phone to get a little warm, can you stress it enough to get the phone so hot that it throttles?
Some notes to set the stage:
- I’m a designer, but these are not design critiques or new features you should add — these are strictly ways to stress-test what you already have
- This is written without assuming you have access to the source code. These are all bugged states and errors that users can encounter just running your production app on their phones
- This barely touches on accessibility, which is a whole other thing to audit for. For more info, I suggest checking out:
- mobilea11y, which focuses on accessible development practices, and
- AppleVis, which focuses on users with low vision. It’s a good place to see what users expect, care about, and appreciate from the products they use.
- This is not AI-written, I just really like using em dashes.
Navigation and Transitions
- Can I get a race condition between programatic NavigationPath updates and my own back swipe gestures?
- Rapidly push → pop → push the same route; pop with edge-swipe while an async load starts
- Can I push a new screen in the middle of a transition?
- In sheets, try to get the sheet dismissal drag gesture to conflict with gestures within the sheet (scrolling or panning or magnifying)
- In a NavigationSplitView, deselect a sidebar item and see how the main content pane reacts to having no selection. Does it update? Does it show an empty state? Is it just blank?
- In a sidebar app, can we get it to forget state/lose focus?
- i.e. in an outline view, can you get it to forget what’s selected and what sections are collapsed?
- In a tab bar app, check if tapping the tab bar does the expected behavior of pushing you to the tab’s root view (or scroll to the top if you’re already there)
- It’s not system behavior but common enough that users expect it
onAppear
/task
duplication- Navigate to a view then immediately go back and forward again rapidly
- Open/close sheets and modals repeatedly
- Try to pull-to-refresh multiple times before first one completes
- Switch tabs rapidly while data is loading
- Background/foreground app while loading screens are visible
Input and Forms
- Try to break focus state
- Tap a text field → rotate device → check if keyboard still there
- Fill a form field → submit → see if focus advances properly
- Using an external keyboard, hit Tab repeatedly
- Keyboard dismissal
- Can I scroll to dismiss a keyboard?
- Does the keyboard stay up even when I don’t want it to?
- Does the keyboard ever cover elements that I need to access?
- TextField/TextEditor jank
- Paste a big chunk of text and see if the app starts to choke
- Check if it uses the right
textContentType
(especially during onboarding like OTPs, user’s name, email, etc)
- Data loss on app background
- Start typing in a form, switch apps, then return. Did the in-flight value survive?
System Integration
- How does the app work in iPhone landscape?
- This is one of the easiest things to try and the source loads of UI bugs. I’m serious, flip your phone to landscape and double tap the home indicator to open the Type to Siri interface.
- Check for truncation, form inputs, transition bugs
- Try rotating in different app states (i.e. in a modal sheet)
- If the app uses NavigationSplitView, a bunch of bugs will almost always pop up in iPhone landscape
- Run on an iPhone SE
- Trigger the double height status bar (in-call or personal hotspot)
- Check if your minimum size constraints actually work (or if they need to get added)
- Screwing with refreshable
- Can I get the refresh to stall if I leave the view? Dismissing the sheet, changing tabs, pushing a new screen, backgrounding the app
- Same thing with rotating — what happens if I refresh and rotate? Or refresh while the keyboard is up?
- In a scroll view, tap the status bar repeatedly to jump to the top. Does the scroll-to-top lurch if you keep tapping it while it’s animating?
- During first launch, what happens if the user declines granting the app permission for something? Can you handle that gracefully?
- If you app sends push notifications, have you told the system where your notification preferences are in the app?
- Long press everything that looks remotely interactive
- Check if context menus appear where expected (and don’t where they shouldn’t)
- Sometimes the system flips the order of context menu items based on if the menu is presented above or below the presenting element. Make sure this is the right behavior for your menus and, if not, check out the menuOrder APIs
- Drag and drop
- Can I drag things from the app? Is it vending the right kind of data?
- Does the app have drop destinations where I’d expect them to?
- E.g. a file in a documents app or an asset from Photos getting dropped into a photo picker element
- The composer in the Twitter app had a nasty drag/drop bug for ages were it wouldn’t resolve any images (the intent was clearly to attach an image to a tweet) but instead it would insert the local file URL wherever your cursor was and you’d get a tweet like:
Hey have you ever noticed how file:///var/mobile/Library/SMS/Attachments/02/02/9AEC5611-5B71-44D1/Untitled.heicwhen you try to drag an image on a tweet it breaks?
- Time and locale shenanigans
- Simulate someone reconnecting to the network after landing from a flight by changing the timezone while the app is running. Does the app handle relative timestamps correctly?
- Toggle 24-hour time and check if the app respects that preference
- The “Language & Region” setting screen gives you a lot of levers to pull to see where an app hard-codes assumptions
- I personally have the date format set to ISO YYYY-MM-DD instead of the US default MM/DD/YY. I see a lot of apps that don’t respect that setting
- Switch the “Start Week On” setting between Sunday/Monday
- Use your app through iPhone Mirroring! This catches a lot of extra issues around text input, pasteboards, and gesture handling.
Performance and Stress
- Can I trigger ForEach identity issues?
- In a list, rapidly delete multiple items in random order
- Add items then immediately sort/filter
- Reorder items quickly then background the app
- Do a row update (favorite/bookmark/edit) and quickly scroll
- Beyond identity issues, how does the app handle scrolling a list super fast?
- Long press the scroll indicator and fling it to the bottom to scrub the scroll view
- With an infinite scroll view, test how this works if you grab the scroll indicator and keep it pressed against the bottom
- Use the app with Low Power Mode enabled
- See where things become unbearably slow
- Look for behaviors that can/should get disabled for low power
- Apple has a good (but old) tech note about reacting to low power mode
- This is tricky to test, but check how your app handles network conditions that float in and out
- I depend on the New York City subway to get around. In stations, I have full cell bars. In the tunnels, I have no signal at all. Is your app able to handle this gracefully?
Accessibility
- Repeat everything on this list with Reduce Motion, Reduce Transparency, Increase Contrast.
- Then do it again with all three turned on
- Then do it again with all three turned on but in dark/light mode (whichever you normally don’t use)
- Check if custom colors respond to dark mode/increased contrast correctly. These are really visible with brand colors/palettes
- Try using the Bold Text accessibility option and see how your app reacts. Is everything relatively bolder? Or is everything the same font weight now?
- Bold Text should make everything proportionally bolder, not flatten all weights into one mushy medium
- Add the Text Size control to your Control Center, then set it to only adjust your app.
- See where your layout starts to break
- Look for areas where they don’t respond to the new font size (either they’re hard-coded or you’re not observing
traitCollectionDidChange
— these will become apparent when you force quit the app and relaunch it)
This is all to say: your app will break. Users will find ways to torture it that you never imagined. They’ll rotate mid-animation, background during critical saves, and somehow end up with seventeen modal sheets stacked on top of each other. They’ll be on a train that goes underground every three minutes with 5% battery remaining and every accessibility setting cranked to eleven. They’ll long-press buttons you forgot were even there.
The good news is you can find these bugs first. The better news is that it’s oddly satisfying when you do. Each crash prevented is a one-star review avoided, every paper cut smoothed is a user who doesn’t bounce. Happy hunting.