skip to content

Framework Architecture

How an automation codebase holds up as it grows: module dependency direction, session lifetime, configuration resolution and run evidence. Interviewers use it to tell engineering from scripting.

on this pageshow

explore

questions

page 1 of 2

A shared base class prepares every case in a suite — what does a case pay for setup it never uses?

level: juniorimportance: must knowfreq 62%

answer

  1. Nobody opted in
  2. The steps run whether needed or not
  3. Time cost multiplied by case count
  4. Unrelated preparation failure reddens the case
  5. One commit at a time, never removed

basics

~10 s

An unused inherited setup step costs runtime and reliability: it still runs on every execution, so the case is slower and can fail for reasons unrelated to what it checks.

solid answer

~50 s

Inherited preparation is unconditional: if the base class signs a user in, seeds account data and starts capture, every descendant pays for all three whether or not it touches them. The direct cost is wall-clock time multiplied by the case count. The larger cost is a widened failure surface — a case that only checks a validation message goes red when the seeding step breaks, and the report names the case, not the step. Diagnosis slows too, because the reason each step exists is not written anywhere the case can see; a reader has to walk the chain upward to learn why an account exists. Ancestors reach this state one commit at a time: each new suite needs one more thing, the shared parent is the cheapest place to put it, and nothing is ever removed.

code

pseudocode · 13 lines
pseudocode
class CaseBase:
    prepare():
        sign_in(default_user)        # added for the checkout suite
        seed_account(plan = "basic") # added for the billing suite
        set_toggle("new_nav", on)    # added for the navigation suite
        start_capture()              # added after a red week

class EmptyEmailIsRejected extends CaseBase:
    run():
        open_signup_form()
        submit()
        expect_message("email is required")
        # inherits four preparation steps, reads none of them

go deeper

for a junior

Be ready to say what your own case inherits and why. Know that preparation defined in a parent class runs whether your case uses it or not, which makes your case slower and gives it more ways to fail.

for a middle

Explain the two distinct costs — runtime multiplied by the case count, and a failure surface that reddens cases which never touched the broken step — and describe how the ancestor accumulated its steps one commit at a time.

for a senior

An interviewer expects you to spot the pattern in review before it hardens: chosen values and guards inside shared preparation, thin subclasses that exist only to skip a step, and preparation that outweighs the behaviour being checked.

for a principal

Own the rule that decides where new shared preparation lands, and argue it in cost terms: the blast radius of a preparation defect equals the size of the subtree beneath it, so implicit reuse prices every future change.

## What inheriting setup actually means Most harnesses put shared preparation in a **base class**: a class every case class extends, holding steps the runner executes before each case. A descendant acquires those steps simply by existing in the chain. It does not opt in, it does not list what it received, and it usually cannot see the steps without opening the ancestor and reading it. That single property — **acquisition without declaration** — is what makes the arrangement so cheap to add to and so expensive to live in. Adding a step to the ancestor is one commit and immediately serves every suite. Nothing in the design pushes back. ## How one ancestor ends up carrying everything The growth is never a decision; it is a sequence of individually reasonable ones: 1. The first suite needs a signed-in user. Sign-in moves into the base class, two suites share it, and the change is an obvious win. 2. A second suite needs seeded account data. Putting it in the shared ancestor is a one-line change; giving that suite its own parent is a refactor with a review attached. The one-liner wins. 3. A third suite needs a feature toggle flipped. Same argument, same outcome. 4. A red week prompts someone to add artefact capture — a screenshot, a log tail, a request trail — to the ancestor, because it is the one place guaranteed to run for everything. 5. The suite that needed step 2 is deleted, but the seeding step stays, because nobody can prove that no remaining case depends on it. Every commit is locally correct. The accumulated result is an ancestor whose steps no individual case can justify, and which nobody feels entitled to touch. ## The four bills a case pays **Runtime, multiplied.** Suppose sign-in costs 3 seconds, seeding 2 and toggling 1, and the suite holds 400 cases. That is 40 minutes of preparation per run, and for a case that only checks a validation message on an anonymous form, its whole share of that is waste. Parallel execution hides the wall clock but not the machine cost. **A widened failure surface.** Every inherited step is another way for an unrelated case to go red. When the seeding call starts returning an error, every case beneath the ancestor fails — including the ones that never read seeded data — and the report names the cases, not the step. The blast radius of a preparation defect equals the size of the subtree beneath it. **Slower diagnosis.** The reason each step exists is not written down anywhere the case can see. A reader who wants to know why an account exists before their case runs must walk the chain upward, and the commit that added the step is usually years old with a message that explains nothing. **Lost isolation.** A case that inherits four preparation steps cannot be lifted into another suite, cannot be run against a target where one of those steps is impossible, and cannot be read on its own. Its real preconditions are unstated, so nothing can check that they still hold. | Property | Setup inherited from an ancestor | Setup named by the case itself | |---|---|---| | How it is requested | implicitly, by extending | explicitly, by naming it | | Where you read it | walk the chain upward | at the top of the case | | Cost when unused | paid on every run | not paid at all | | Failure attribution | the case is blamed | the named step is blamed | | Adding one more | one line, affects everything below | one line, affects one case | ## Symptoms worth naming in review - The ancestor's preparation is longer than the average case body. - A step is guarded by a value descendants set, which is the hierarchy admitting the step is not universal. - Someone has added a thin subclass whose only purpose is to skip part of the ancestor. - Deleting a step is blocked because nobody can enumerate its dependants. - Average case runtime is dominated by preparation rather than by the behaviour under check. ## Keeping the next step out Prevention is much cheaper than cure, and it is a rule about **where new shared code lands** rather than a rewrite: - New shared preparation goes into a small named unit that a case calls by name; the ancestor does not grow. - Anything that is not true of *every* descendant does not belong above them. - A step that needs a value the case chooses is case-specific by definition — parameters are the tell. - Keep the ancestor to work that is genuinely invariant and unconditional, and prefer one level of depth so the effective behaviour is one hop away. Unpicking a step that live cases already rely on is a separate and larger piece of work with its own sequencing. The point here is to stop the pile growing while you still have the choice.

  • How would you measure what the inherited preparation actually costs this suite?
    Time the preparation separately from the case body and emit each step's duration as part of the run's own output. Multiply the per-case total by the case count, then split it by which steps the case actually reads. The gap between those two numbers is the waste, and it is usually large enough to end the argument without appealing to taste.
  • A step in the ancestor is guarded by a value that subclasses set. What is that telling you?
    That the step was never universal, and the hierarchy is being asked to express something it cannot. A guard is an opt-out bolted onto a mechanism whose whole premise is that descendants do not choose. The honest form is a named unit the case invokes when it wants the behaviour; the guard form hides the default and grows a second guard the next time.
  • Why is deleting an inherited step harder than adding one?
    Because addition is verified by the suite going green, while deletion has to prove a negative: that nothing below relies on it. Nothing records which descendants read the state a step produced, so the only evidence is a full run — and if a case fails afterwards, the cause looks like the deletion rather than the declaration that was always missing.

A shared ancestor is like a service charge added to every room in a hotel: each addition was justified by one guest, and everyone pays it forever.

saying these in an interview costs you the question

  • Says shared preparation is always good because it removes duplication
  • Claims unused inherited steps are free because they are fast
  • Blames the failing case rather than the preparation step that broke
  • Treats the base class as the natural home for any new shared step
  • Cannot say which inherited steps their own case actually depends on
open as a page

In an automated case, why should record provisioning happen in a setup step rather than between the assertions?

level: juniorimportance: must knowfreq 62%

basics

~10 s

Setup that breaks is not a failed check. Keeping provisioning in one step before the assertions lets a run report could-not-build-the-premise separately from the-product-behaved-wrongly — two different problems with two different owners.

open as a page

Should a driver session be created per case, per class, or per parallel test worker, and what does each cost?

level: juniorimportance: must knowfreq 66%

basics

~20 s

Default to a fresh driver session per case: it is the only lifetime that guarantees a clean start. Reuse across a class or a parallel worker only when acquisition is measurably slow; one case can then poison the next.

open as a page

Why should an automated suite select a deployed target by name rather than carrying its address in the case?

level: juniorimportance: must knowfreq 64%

basics

~20 s

A named target is one indirection point the run resolves once, so the same suite can be pointed anywhere. An address written into a case pins that case to one deployment and must be found and edited when it moves.

open as a page

In what order should a test run resolve configuration from defaults, files, process environment and explicit overrides, and why fix that order?

level: middleimportance: must knowfreq 58%

basics

~20 s

Resolve configuration once at startup in one fixed, documented order: built-in defaults, then a checked-in file, then process environment variables, then explicit overrides, each layer replacing the last. A fixed order makes every run's effective settings predictable.

open as a page

Why must a harness capture failure evidence during the failing run rather than let an engineer reproduce it?

level: middleimportance: must knowfreq 58%

basics

~20 s

The failing execution is the only one holding the evidence. Teardown discards the product state the case saw, an intermittent failure may not recur, and a re-run meets different data and timing. Capture therefore belongs in the harness's failure path.

open as a page

How do you deprecate a shared business-action signature that hundreds of live cases call, without a flag day?

level: middleimportance: must knowfreq 55%

basics

~20 s

Add the new signature beside the old one, make the old one delegate to it so behaviour cannot fork, move callers in reviewable batches, reject new uses once none are arriving, then delete the old form entirely.

open as a page

Which import direction between an automation suite's cases, business actions and protocol adapters keeps the layering intact?

level: middleimportance: must knowfreq 62%

basics

~20 s

Imports point one way: cases import business actions, actions import protocol adapters, and nothing imports back upward. An adapter that knows about a case, or a case that calls an adapter directly, has broken the layering.

open as a page

A parallel run can dispatch by case, by class or by process — what does each unit make contended?

level: middleimportance: must knowfreq 58%

basics

~20 s

The unit of parallelism decides what two test workers can touch at the same moment. Dispatching whole classes protects per-class setup; dispatching individual cases contends it; separate processes end in-memory sharing but leave every external resource shared.

open as a page

Why must the command that runs a suite exit non-zero when a case fails, and what does an always-zero exit cost?

level: middleimportance: must knowfreq 68%

basics

~10 s

A pipeline reads only the invoked command's exit status to decide pass or fail. An always-zero exit makes every stage green regardless of results, so failures are recorded in reports nobody blocks on.

open as a page

Which statuses beyond pass and fail should an automated suite record for a case, and what does each let a reader decide?

level: middleimportance: must knowfreq 62%

basics

~20 s

Record at least skipped, blocked, known-issue and flaked alongside pass and fail. Each answers a different reader question: was the case deliberately not run, prevented from running, failing for an accepted reason, or unreliable and not evidence of anything?

open as a page

Where can a retry sit in an automated suite — around an action, around a case, or around a whole run — and what does each placement conceal?

level: middleimportance: must knowfreq 62%

basics

~20 s

A retry can wrap a single action, a whole case, or an entire run. The wider the wrapper, the more it conceals: an action-level retry hides that a step was unstable, a run-level retry hides which case failed at all.

open as a page

A harness keeps the current case's test user in one process-wide variable; why does that break once two test workers run at once?

level: middleimportance: must knowfreq 60%

basics

~20 s

Both workers write the same variable, so a case reads whatever the other stored last. Serial execution was the only thing making that safe. The result is wrong-data assertions and cross-talk between cases, not a crash.

open as a page

Why should an automated run print the configuration it actually resolved before the first case runs?

level: juniorimportance: should knowfreq 44%

basics

~20 s

So anyone reading the output later sees the values the run truly used, not the ones a file suggests. Without that echo, explaining a surprising result means reconstructing every input layer by hand, on a machine that is gone.

open as a page

Why does a failure record need both the actual-versus-expected pair and the log of steps that led there?

level: juniorimportance: should knowfreq 52%

basics

~20 s

The value pair says what is wrong — the discrepancy itself. The step log says how the run got there — which interactions ran, what each returned, and how long they took. Neither answers the other's question.

open as a page

What do you gain by passing a case's test data into a helper as parameters instead of letting the helper read a shared holder?

level: juniorimportance: should knowfreq 40%

basics

~20 s

The helper's inputs become visible in its signature: a reader knows what it needs, a caller can supply different data, and nothing outside the call can change it midway. The cost is longer signatures on every step of the chain.

open as a page

Why does a four-level base-class chain stop an automated case from declaring the setup it needs?

level: middleimportance: should knowfreq 54%

basics

~20 s

Depth converts a requirement into a location: what a case gets is decided by where it sits in the chain, not by anything it states. Composition returns the declaration — the case names the units it uses.

open as a page

Why should an automated case ask its provisioning seam for an account already in the condition it needs, rather than name a known record identifier?

level: middleimportance: should knowfreq 55%

basics

~20 s

Naming the condition — an account whose trial has ended — lets the seam satisfy it on any deployed target and puts the case's premise in the case. A pinned identifier assumes a record whose meaning can drift unnoticed.

open as a page

A case opens a driver session, then a preparation step throws before the first assertion. What guarantees the session is closed?

level: middleimportance: should knowfreq 47%

basics

~20 s

Only 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.

open as a page

When a driver session is reused across cases, what must be reset between them, and what breaks when it is not?

level: middleimportance: should knowfreq 54%

basics

~20 s

Reset everything the driver session carries: stored client data, the current location, open overlays, granted permissions, display size, and any per-case connection settings such as a shortened wait budget. Miss one and you get order-dependent failures in the wrong case.

open as a page

What makes an extension point in an automated suite different from shared code everyone edits?

level: middleimportance: should knowfreq 46%

basics

~20 s

An extension point is a declared contract the framework itself calls: a named operation, a registration step, and a stated moment in the run. Shared code everyone edits has none of those, so every change reaches every case.

open as a page

Where may an assertion live in a suite layered into cases, shared business actions and protocol adapters?

level: middleimportance: should knowfreq 50%

basics

~20 s

Verdicts about product behaviour belong in the case, which names the claim being proved. A shared business action may only guard its own postcondition, reported as a broken arrangement, and an adapter judges nothing about behaviour.

open as a page

What must each parallel test worker hold uniquely for a case to run safely beside a copy of itself?

level: middleimportance: should knowfreq 48%

basics

~20 s

Anything the case names by a constant is a collision candidate: the identity it signs in as, any fixed local port it binds, the paths it writes evidence to, and any single record or global setting it changes in place.

open as a page

Adding a per-case setup step to a subclass breaks cases that passed before — how does inherited setup ordering explain it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Preparation spread across a class chain runs in an order no single file states; it comes from the runner's rule and from whether each level invokes its parent. A new step can land ahead of what it assumed.

open as a page

When a required configuration value is missing at the start of an automated run, should the run fail immediately or fall back to a built-in default?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Fail immediately, before the first case, naming the missing key and the layers searched. A silent default turns a configuration mistake into either a green run against the wrong thing or a wave of failures that blame the product.

open as a page

Why is the provisioning port an automated case obtains its records through worth designing before the implementation behind it?

level: seniorimportance: should knowfreq 46%

basics

~20 s

The port is the contract every case is written against; the mechanism behind it is replaceable. Design the request vocabulary first and cases survive when record creation moves from direct writes into the store to the product's own creation interface.

open as a page

In a suite that can be pointed at a protected deployed target, where should the refusal to run destructive cases live?

level: seniorimportance: should knowfreq 46%

basics

~20 s

In one shared gate the runner applies before every case, keyed off an attribute on the resolved target and a marker on the case. A guard each author must remember to write is a guard that will be forgotten.

open as a page

What does a conditional on the deployed target's name inside a case body cost a suite over a year?

level: seniorimportance: should knowfreq 41%

basics

~20 s

Each branch adds a path that a single run cannot exercise, so the untaken one rots unnoticed. Over a year the case stops describing the product and starts describing the deployments, and nobody can say what it actually verifies.

open as a page

When a suite discovers its extensions by scanning instead of an explicit list, what breaks at debugging time?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Scanning makes the active set invisible: no file names it, call order follows the scan, and a stray implementation on the search path runs everywhere while a missing one goes unnoticed. Publishing the resolved set restores traceability.

open as a page

How do you correlate a failed automated case with the system's own record of the same request?

level: seniorimportance: should knowfreq 41%

basics

~20 s

Mint an identifier per case, attach it to every outbound call in a field the system propagates through its own layers and writes on its log lines, and print it with the failure. The report then points straight at the system-side records.

open as a page

showing 1–30 of 55