In a Selenium suite, what breaks when the WebDriver is created in a per-test setup hook but quit in a class-level teardown hook?
answer
- Count the births against the deaths
- One field, overwritten before every test
- Only the last reference survives to teardown
- Dropping a reference sends no delete-session
- Creation hook and quit hook, same scope
basics
~20 sEvery test after the first orphans a browser session. The class-level teardown quits only the last driver the field still holds, so the earlier sessions stay alive, holding browser processes and Grid slots until something else reaps them.
solid answer
~50 sA per-test setup hook constructs a new `WebDriver` for every test method and overwrites the reference the previous test left in the field, so by the time a class-level teardown runs there is exactly one live reference — the last one. `driver.quit()` there deletes that single session; every earlier session is orphaned, with its browser process, its temporary profile directory and, on a Grid, its slot still held until the Node's inactivity timeout reaps it. Thirty tests in a shipping-label printer class means thirty sessions and one `quit()`. The rule at this seam is that the hook that constructs the driver and the hook that quits it must sit at the same scope: per-test creation pairs with per-test teardown, a class-scoped fixture pairs with a class-scoped teardown. Mixing them is not a tuning choice, it is a leak.
code
java · 30 linesimport org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
class ShippingLabelPrinterTest {
private WebDriver driver;
@BeforeEach
void openPrinterPage() {
driver = new ChromeDriver();
driver.get("https://warehouse.example.com/shipping/label-printer");
}
@AfterEach
void endSession() {
if (driver != null) {
driver.quit();
}
}
@Test
void printsLabelForOrder() {
driver.findElement(By.id("order-id")).sendKeys("SO-40199");
driver.findElement(By.id("print-label")).click();
}
}go deeper
Be ready to say where the driver is started and where it is quit in your suite, and to name the setup and teardown hooks your runner gives you. Knowing that the browser closes only on an explicit quit call is the floor here.
An interviewer expects you to explain the mechanics: the field is overwritten on every test, the previous session is never told to end, and only the last reference reaches the class-level teardown. Give the arithmetic of thirty starts and one quit.
Show the operational consequence. Talk about accumulating browser and driver processes, temporary profile directories, and Grid slots that stay occupied until an inactivity timeout frees them, and about a suite that reports green while degrading the host it runs on.
Own the rule rather than the incident: creation and destruction hooks are paired by scope, and that pairing is a reviewable property of a harness. Be ready to say how you would keep it from drifting again as the suite and its runners change.
## Two hooks, one lifetime A `WebDriver` object in Selenium is a **client-side handle** over a session that lives on the remote end — a driver process such as ChromeDriver, or a Grid Node. The session is **born** when the constructor runs (`new ChromeDriver()`, or `new RemoteWebDriver(url, options)`), which sends the new-session request, and it **dies** only when `driver.quit()` runs, which sends the delete-session command. Nothing else ends it. So a test runner offers exactly two attachment points that matter at this seam: the hook that runs the constructor, and the hook that runs `quit()`. Each of those hooks has a **scope** — how often the runner fires it: - a **per-test hook** fires once around every test method: `@BeforeEach`/`@AfterEach` in JUnit 5, `@BeforeMethod`/`@AfterMethod` in TestNG, `[SetUp]`/`[TearDown]` in NUnit, or a pytest fixture declared `scope="function"`; - a **class- or session-scoped fixture** fires once around a whole class or a whole run: `@BeforeAll`/`@AfterAll`, `@BeforeClass`/`@AfterClass`, `[OneTimeSetUp]`/`[OneTimeTearDown]`, or a pytest fixture declared `scope="class"` or `scope="session"`. The bug in this question is a **scope mismatch**: births at per-test scope, deaths at class scope. ## What the mismatch actually does Take a `ShippingLabelPrinterTest` class with a `private WebDriver driver;` field and thirty test methods that each print a label for a different carrier. The sequence is: 1. The per-test setup hook runs, calls the constructor, gets **session 1**, and assigns it to `driver`. 2. Test one runs and passes. 3. The per-test setup hook runs again, gets **session 2**, and **overwrites** `driver` — the only reference to session 1 is gone, but session 1 itself is untouched, because dropping a Java reference sends no HTTP request. 4. Steps 2 and 3 repeat twenty-eight more times. 5. The class-scoped teardown finally runs and calls `quit()` on the one reference still in the field: **session 30**. Twenty-nine live browsers are left behind, and the suite reports a clean pass while doing it. On a laptop the symptom is a machine that slows to a crawl part-way through the run; the failure is silent until something runs out. ## What an orphaned session is still holding - The **browser process** and its renderer child processes, with their share of RAM. - The **temporary user-data directory** the driver created for that session's clean profile. - The **driver process** that owns the session, if one was started per driver. - On a Grid, the **slot** the session was assigned. The Node frees it only when its inactivity timeout kills the session, so every queued request behind it waits for a timer instead of a test. - Nothing that the JVM can reclaim for you: the discarded `WebDriver` object is ordinary garbage, and garbage collection does not speak the WebDriver protocol. ## The pairing table Read this as a rule about *pairs*, not about which scope is better — the scope choice is a framework-design question; the pairing is not negotiable once you have made it. | Where the driver is created | Where `quit()` must run | Result across a 30-test class | |---|---|---| | per-test setup hook | per-test teardown hook | 30 sessions created, 30 quit — correct | | per-test setup hook | class-scoped teardown | 30 created, 1 quit — 29 orphans | | class-scoped fixture | class-scoped teardown | 1 created, 1 quit — correct | | class-scoped fixture | per-test teardown hook | 1 created, quit after test one — tests 2-30 fail | That last row is the mirror-image defect and it is the loud one: after `quit()` the session no longer exists, so the next test's first command fails immediately rather than leaking quietly. A leak that fails fast is easier to find than a leak that passes. ## Getting the pairing right 1. Decide the driver's scope once, and put the constructor in the hook at exactly that scope. 2. Put `quit()` in the **mirror** hook at the **same** scope, guarded by a null check so a session that never started does not mask the real error. 3. Make the reference visible to both hooks and to nothing coarser — a field on the test instance for a per-test driver, a per-worker holder when the runner dispatches tests to several workers at once. 4. Never leave the constructor in one place and the `quit()` in another as a temporary measure; nothing about this seam is temporary. A useful review habit for a shipping-label suite: count the calls. If the class can construct a driver thirty times and can only reach `quit()` once, the arithmetic is the bug, and no amount of pipeline tuning will fix it.
- Why does dropping the reference to a WebDriver not close the browser?Because the `WebDriver` object is only a client-side handle over a session owned by the remote end. Reassigning or garbage-collecting it sends no request, so the driver process and the browser keep the session open. The delete-session command is issued by `quit()` and by nothing else, which is why the teardown hook has to run it explicitly.
- Does calling quit() twice on the same driver cause a problem?No. In Selenium's Java client a second `quit()` on an already-quit driver is a no-op, so a defensive `quit()` in a teardown hook is safe. The dangerous direction is the other one — a session that is never quit at all. That is why a null-guarded `quit()` in the mirror hook is the normal shape rather than clever bookkeeping.
- The same test needs a second browser for the warehouse operator's screen. What changes at the hooks?Nothing structural: the setup hook constructs both drivers and the teardown hook quits both, each guarded by its own null check. The pairing rule applies per driver, not per class. The mistake to avoid is quitting only the one the last assertion used and letting the other run on.
saying these in an interview costs you the question
- Thinks a class-level teardown quits every driver the class created
- Believes garbage collection closes a browser once the reference is dropped
- Says an orphaned session is harmless because the tests still passed
- Assumes a Grid frees the slot the moment the test method ends
- Treats the mismatch as a slow-machine problem rather than a leak