After an incoming call covers a flow and is dismissed, what should an automated case check?
answer
- The platform draws it, not the app
- Raise the interruption from outside the app
- Assert the resume, not the dismissal
- Declined and answered differ in depth
- Check nothing advanced or re-sent itself
basics
~20 sCheck that the flow resumed at the same step with the same entered values, that nothing advanced or re-sent itself while the app was inactive, that countdowns and media behaved as specified, and that nothing sensitive was left exposed.
solid answer
~40 sAn incoming call is drawn by the platform, not by the app, so the app is pushed into an inactive or fully backgrounded condition it neither chose nor controls. The case has to raise the interruption from outside the app - a trigger the run owns - and act on it in the platform layer, because the call controls are never in the app's own element tree. Once it is dismissed, assert the resume: the same step renders with the same entered values, input focus returns somewhere sensible, and any countdown reflects real elapsed time instead of restarting. Then assert what must **not** have happened: no step advanced by itself, no request sent twice, no upload silently abandoned. Note that a declined call and an answered-then-ended call are different depths and can resolve differently.
code
pseudocode · 15 linescase "checkout survives an incoming call":
advance_to(step = "payment", with = seeded_card_reference)
expected = read_rendered_screen()
ring_device(from = run_owned_number) # drawn by the platform, outside the app
wait_until(platform_call_screen_visible)
in_platform_layer: # the app's element tree has none of this
decline_call()
wait_until(app_in_foreground and screen_settled)
assert read_rendered_screen() == expected
assert payment_attempts_for(order_id) == 0
assert focused_field() == "card_number"go deeper
Know that a call or a system alert is drawn by the platform over the app, and that the app cannot control when it appears or leaves. Recall that a case must check the flow afterwards, not merely that the app came back.
Explain that the interruption is raised from outside the app and acted on in the platform layer, and that the app's element tree never contains it. Describe which rendered values you compare before and after, and why a declined call and an answered one differ in depth.
Demonstrate judgement about the negatives - no self-advancing step, no duplicate request, no silently abandoned upload - and about resume-versus-restart rules for long interruptions. Be ready to say which flows earn a case this slow and which do not.
Own interruption behaviour as a product-wide rule rather than a per-screen accident: which flows must survive, what expiry applies to anything held, and how much suite time this class of case deserves against how often the condition really bites.
## An interruption is not a background the app chose Backgrounding is usually something the person does to the app. An interruption is something the platform does to both of them. A call arrives, an alarm fires, a system warning appears, and the app is pushed out of the foreground - or merely covered and made inactive - without any input the app can influence. It comes back when the interruption ends, and the only interesting question is what the flow looks like at that moment. The depth of the interruption varies, and the variation is the whole reason the case is worth writing. A declined call may take the foreground for a second. A call that is answered and then ended may leave the app in the background for minutes, long enough for the platform to reclaim its process altogether, so the same scenario can resolve into a resume on one run and a rebuild on another. | Interruption | Who draws it | Typical depth | What the case checks on return | | --- | --- | --- | --- | | An incoming call, declined | the platform | a brief inactive spell | the flow is unchanged and no step advanced | | An incoming call, answered then ended | the platform | a full background, possibly minutes | resume or rebuild per the product's rule, timers consistent | | An alarm or reminder alert | the platform | a covering overlay | nothing behind the overlay was acted on | | A low-power or system warning | the platform | an overlay only | the screen underneath is untouched | | An arriving notification banner | the platform | a strip over an active screen | taps still land on the app, not on the banner | ## Raise it from outside the app Because the interruption belongs to another process, it is not in the app's element tree. A case that searches the app's own tree for the answer or decline controls simply waits until it gives up, and that timeout is then usually mislabelled as flakiness. The harness has to move to the platform layer, act there, and move back. That structure - arrange in the app, act in the platform layer, assert in the app - is what makes these cases readable, and it is worth writing as an explicit helper so every interruption case has the same shape. The trigger itself should be something the run owns. An interruption raised by whatever happened to arrive on a shared device is not reproducible and will not be there tomorrow. ## What to assert once it is dismissed Capture the rendered screen before raising the interruption and compare afterwards. The comparison covers: - **The same step** of the flow, not one before or after it. - **The same entered values**, including anything unsaved. - **Input focus** somewhere sensible, so the person can carry on typing rather than hunting. - **Time-dependent elements** consistent with the real elapsed time, rather than restarted from zero or frozen at the instant the interruption began. - **Media, capture or recording** in whatever condition the product promises - resumed, paused with a visible control, or stopped with a visible explanation, but never silently dead. And the negatives, which is where the real defects hide: 1. No request fired twice because a resume path re-ran an action. 2. No step advanced on its own while the app was inactive. 3. No upload or recording abandoned without the person being told. 4. Nothing sensitive left rendered in whatever preview the platform keeps of a backgrounded app. ## Deciding whether to resume or restart Some flows should come back exactly as they were; others are better restarted cleanly. The rule follows the cost to the person, not the ease of implementation. A long form with unsaved typing must resume, because restarting it means retyping everything. A short read-only screen can be rebuilt from scratch with no loss. Anything holding a reservation, a quoted price or an in-progress payment needs an explicit expiry rule, because resuming into an expired hold is worse than restarting: it looks correct and fails at the end. Whichever rule the product chooses becomes the assertion. The case is not there to discover the rule; it is there to hold the product to it. ## Which flows earn this case Interruption cases are slower and more fragile than ordinary ones, so they should be spent where loss actually hurts: long forms, payments, uploads, anything holding a limited resource, anything with a countdown. A browse screen does not need one. Writing the case for every screen is how the suite becomes slow enough that someone eventually deletes all of them, including the three that mattered.
- Why can a case not find the call screen's controls in the app's own element tree?Because the interruption is rendered by a different process that owns the foreground, and the app's tree describes only what the app draws. The harness has to move to the platform layer, act there, and move back. A case that searches the app's tree simply waits until it gives up, and that timeout usually gets mislabelled as flakiness rather than as a structural mistake.
- How do you decide whether the product should resume the flow or restart it after a long interruption?From the cost to the person, not from what is easy to build. A short read-only screen can be rebuilt from scratch with no loss. A long form with unsaved typing must resume, or everything is retyped. Anything holding a reservation, a quoted price or a payment in progress needs an explicit expiry rule, and whichever rule is chosen becomes the assertion the case enforces.
- How would you exercise this on a device that cannot receive calls?Substitute an interruption that platform can raise - an alarm, a reminder, a system warning - and keep the assertions identical, because the case is really about the foreground being taken away and given back. Record which interruption the run used, so a green result is not read as coverage of a depth it never actually reached.
A doorbell mid-conversation: someone else decides when you stop, and the only thing worth checking is whether you carried on the sentence you were in.
saying these in an interview costs you the question
- Looks for the call controls inside the app's element tree
- Asserts only that the app returned to the foreground
- Treats a declined call and an answered one as identical
- Ignores requests that fired while the app was inactive
- Never checks whether a countdown restarted from zero
- Relies on an interruption that happened to arrive on a shared device