A case opens a driver session, then a preparation step throws before the first assertion. What guarantees the session is closed?
answer
- The happy path is not the only path
- Preparation throws before any assertion runs
- Register the release where you acquire
- Capture artefacts before the connection goes
- Idempotent, scope-bound, never masking
basics
~20 sOnly a release registered at the moment of acquisition, bound to a scope the runner unwinds however the case ends. A close on the case's last line runs on the success path only, so an earlier throw leaks it.
solid answer
~50 sA close on the final line of the case body is reached **only when the body finishes**, and the three exits that matter all skip it: a preparation step throwing after acquisition, an assertion throwing mid-body, and a per-case time limit cutting the case short. The fix is structural — **whoever acquires the session registers its release in the same breath**, against a scope the runner unwinds no matter how the scope ends, so the body never mentions the close at all. Three properties make that registration trustworthy: it is scope-bound, it is idempotent so a double release is a no-op, and it never masks the original failure with its own error. Ordering matters too: capture failure artefacts *before* the release, since the address, the visual state and the collected logs all disappear with the connection.
code
pseudocode · 13 lines# WRONG: reached only if nothing above throws
def case_checkout():
session = acquire_session()
seed_account(session) # throws here -> session leaks
assert_total(session, 42)
session.release()
# RIGHT: release registered at acquisition, artefacts captured first
def acquire_owned_session(scope):
session = acquire_session()
scope.on_exit(capture_artefacts, session) # runs before release
scope.on_exit(release_quietly, session) # idempotent, never rethrows
return sessiongo deeper
Remember that a throw abandons the rest of the case body, so anything written after it does not run. Cleanup that must always happen cannot live on the last line of the case.
Explain registration at the point of acquisition and the three properties that make it trustworthy: scope-bound, idempotent, and non-masking. Be able to list the exits that skip a trailing close, including a preparation step failing before any assertion.
Show the ordering discipline: artefacts captured before the release, release errors logged rather than raised, and a balance check that fails a scope when acquisitions and releases do not match. Explain why helpers that return a session are a design defect rather than a style choice.
Own the convention for the whole suite so no author has to remember it. Decide whether ownership is enforced structurally or by review, and be able to say what the residual failure mode of your choice is and how you detect it.
## Why the last line of the case body is the wrong place A case that acquires a driver session and closes it on its final line closes it **only on the happy path**. Any exit that is not "the body ran to completion" skips the close entirely, and the three that matter all happen daily: 1. **A preparation step throws** — the session came up, then the data setup, sign-in helper or configuration step that follows it failed. The case never reaches its assertions, so it never reaches the close. 2. **An assertion or an interaction throws mid-body.** The remaining statements, including the close, are abandoned. 3. **The case is cut short from outside** — a per-case time limit expires, or the run is cancelled. Nothing inside the body runs at all. The first of these is the one people miss, because it happens *before* the part of the case anyone is looking at. It is also the most common in practice: preparation is where the flakiness lives. ## Bind the release to the acquisition, not to the body The correct shape is a single rule: **the code that acquires the session registers its release in the same breath, against a scope the runner unwinds no matter how the scope ends.** The case body never mentions the close, because the case body is the thing that might not finish. Three properties make that registration trustworthy: - **Scope-bound.** The release is attached to the scope that owns the session's lifetime. If the session is per case, it runs at the end of the case; if it is per group, at the end of the group. Nothing in the body decides. - **Idempotent.** Releasing an already-released session is a no-op, not an error. Otherwise a case that closes early makes teardown throw. - **Non-masking.** A release that fails must not replace the failure that is already being reported. Log its own error and let the original one stand; the alternative is an entire class of failures reported as `close failed` while the real cause is invisible. Ordering matters too. **Failure artefacts must be captured before the release, not after** — a screenshot, the page's current address, the collected logs and the session identifier are all gone once the connection is gone. Registering capture and release together, capture first, is what makes a failed case diagnosable at all. ## Who owns the session — two ecosystem families Stacks answer the ownership question in two distinct ways, and it is worth knowing which one you are in. In the first family, the session is created by a **lifecycle hook the author writes**: a setup routine that runs before each case builds the session, and a matching teardown routine releases it. Ownership is explicit and visible in the file, and the guarantee comes from the runner's promise to run teardown even when the case throws. The failure mode is human: someone writes a case that acquires a second session inline, or copies a class and drops the teardown half. In the second family, the session is **declared as a dependency and injected** by a provider the runner owns. The case receives a ready session and never sees its construction or destruction; the framework closes what it created at the end of the scope the declaration named. The guarantee is structural — an author cannot forget a teardown they never wrote — but the scope is now something you *declare* rather than something you *read*, so a mis-declared scope silently widens a session's lifetime across many cases, and any session acquired outside the provider is invisible to it and leaks freely. | | Author-written lifecycle hooks | Provider-injected session | |---|---|---| | Release guarantee | runner runs teardown on failure | framework owns construction and destruction | | Typical bug | a missing or copied-wrong teardown | a scope declared wider than intended | | Sessions built outside the mechanism | visible in the file | invisible to the framework | ## Two habits that keep it honest - **Never acquire a session in a helper that returns it and walks away.** A helper that constructs a session hands the caller an obligation that nothing enforces. Have the helper take the already-owned session as an argument instead. - **Assert the balance at the end of each scope.** Keep a counter of sessions acquired and sessions released, and fail the scope when the two differ. That check costs a few lines and catches the missing registration on the run that introduces it, rather than weeks later when a long run exhausts the machine. The underlying principle generalises well past tests: **a resource's release belongs to whoever acquired it, expressed at the point of acquisition, in a form the surrounding scope will honour even when the code in between does not finish.**
- Why must the release swallow its own error instead of propagating it?Because it usually runs while a failure is already being reported. If the release throws, the runner reports the release's error and the original cause disappears, turning a whole class of diagnosable failures into an unhelpful cleanup message. Record the release error in the run's log, and let the first failure stand as the reported one.
- Why is capturing failure artefacts after the release too late?Everything worth capturing lives on the far side of the connection: the current address, the visible condition, the collected logs, the session identifier. Once the session is gone, the capture returns nothing or throws. Registering capture before release, so it unwinds first, is what makes a failed case diagnosable at all.
- What goes wrong with a helper that acquires a session and returns it to the caller?It hands the caller an obligation nothing enforces, and every caller has to remember it. Callers copied from other callers forget. Either have the helper accept an already-owned session as an argument, or have it register the release against the scope it was given before it returns.
saying these in an interview costs you the question
- Puts the close on the last line of the case body
- Thinks only assertion failures skip the close, not preparation failures
- Lets a failing release replace the original failure in the report
- Captures screenshots and logs after the session is released
- Acquires sessions inside helpers that return them and walk away