skip to content

Runner Attachment Points

Where a driver is born and destroyed inside a test runner, and why the runner's unit of parallelism has to line up with the scope you gave the driver session.

on this pageshow

explore

questions

3

In a Selenium test class, why is the WebDriver started in the runner's setup hook rather than inside each test method?

level: juniorimportance: should knowfreq 64%

answer

  1. Who opens the browser: the test or runner
  2. The hook fires around every test method
  3. A failed assertion skips the lines below it
  4. Start-up written once, not thirty times
  5. Constructor in setup, quit in teardown

basics

~20 s

The setup hook runs automatically before every test, so each test gets a ready browser session without repeating the start-up code, and the matching teardown hook still runs after a test that failed part-way, closing the browser.

solid answer

~40 s

The setup hook is the runner's attachment point that fires before every test method, so putting the driver constructor there gives each test a fresh session without thirty copies of the same start-up lines. Its mirror, the teardown hook, runs after the test whether it passed or failed, which is where `driver.quit()` belongs: a `quit()` written as the last statement of a test body is skipped the moment an assertion above it fails, and the browser leaks. The hooks also keep the test readable — a shipping-label printer test opens with typing an order id, not with `new ChromeDriver()`. Runners spell the attachment points differently — `@BeforeEach`/`@AfterEach`, `@BeforeMethod`/`@AfterMethod`, `[SetUp]`/`[TearDown]`, or a pytest fixture that yields the driver — but the seam is identical in all of them.

go deeper

for a junior

Be ready to name the setup and teardown hooks in the runner you use and to say what each one does with the driver. Knowing that the browser closes only when quit is called, and that the hook is what calls it, is the expected answer.

for a middle

Explain the mechanics rather than the habit: the hook fires around every test method, a failed assertion skips the rest of the body, and hook placement is what makes cleanup unconditional. Being able to map the hook names across two runners helps.

for a senior

Show what this buys a real suite: no duplicated start-up, no browsers surviving failed tests, and test bodies that read as user steps so a failure points at the application rather than the plumbing. Mention starting each test from a clean profile.

for a principal

Frame it as where a harness puts its seams. The hook is a placement mechanism, not a policy, and the session-scope decision is made elsewhere; be ready to say how you keep that placement consistent across several runners in one organisation.

## What the setup hook is doing at the driver seam In Selenium there are exactly two moments a test runner has to host: the moment a **session** is created, and the moment it is deleted. A session is created by a driver constructor — `new ChromeDriver()` for a local browser, `new RemoteWebDriver(url, options)` against a Grid — and it is deleted by `driver.quit()`. Everything between those two calls is the test. A **setup hook** is the runner's attachment point that fires *before* each test, and a **teardown hook** the one that fires *after* it. Putting the constructor in the setup hook and `quit()` in the teardown hook means the runner, not the test author, is responsible for opening and closing the browser around every test method. The test body is then free to be about the shipping-label printer page and nothing else. ## What each runner calls those hooks The names differ; the attachment point is the same. | Runner | Runs before every test | Runs after every test | |---|---|---| | JUnit 5 | `@BeforeEach` | `@AfterEach` | | TestNG | `@BeforeMethod` | `@AfterMethod` | | NUnit | `[SetUp]` | `[TearDown]` | | pytest | a fixture with `scope="function"`, before its `yield` | the same fixture, after its `yield` | The pytest shape is worth noticing because it is a single function rather than a pair: the fixture builds the driver, `yield`s it to the test, and calls `driver.quit()` on the far side of the `yield`. Selenium's own documentation examples are written that way in Python and with `@BeforeEach` in Java. ## Why the test body is the wrong home for the constructor - **Repetition.** Thirty tests over the label printer means thirty copies of the same start-up lines, and thirty places to edit when the browser options change. - **A failing test skips its own cleanup.** If an assertion fails half-way down a test method, the lines below it never execute. A `quit()` written as the last statement of the body is exactly one of those lines, so a failing test leaks the browser it opened — while the teardown hook still runs. - **Nothing guarantees a starting point.** With start-up inline, one test can leave the printer page in a half-filled state and the next can quietly depend on it. A session created per test starts from a clean profile, which is what Selenium's own guidance means by a fresh browser per test. - **The test reads like plumbing.** The interesting lines — type the order id, choose the carrier, click print — get buried under construction and cleanup. ## What belongs in the hook, and what does not 1. **Belongs:** constructing the driver, applying the browser options, and navigating to the page under test — `driver.get("https://warehouse.example.com/shipping/label-printer")` is a reasonable last line of a setup hook when every test in the class starts there. 2. **Belongs in the teardown hook:** `driver.quit()`, guarded so that a run in which the driver never started does not throw a second, confusing error over the first. 3. **Does not belong:** assertions. A failed assertion inside a setup hook is reported as a broken fixture rather than a failed test, and every test in the class inherits the same red mark. 4. **Does not belong:** anything a single test needs and the others do not. Per-test detail stays in the test method; the hook holds only what is common to all of them. ## The shape in practice For a `ShippingLabelPrinterTest` class the whole seam is four lines of plumbing and no more: ```java 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(); } } ``` Each test method now opens with a real user step. When a label fails to render, the failing line is a click on the printer page rather than a driver constructor, and the browser is closed whichever way the test ended. ## The one thing the hook cannot decide for you The hook is a **placement** mechanism, not a policy. How wide the session's scope should be — one per test, one per class, one per parallel worker — is a framework-design decision made elsewhere, and this seam simply obeys it: whatever scope you pick, the constructor goes in the hook at that scope and `quit()` goes in its mirror. Getting the placement right is what keeps the decision honest.

  • What happens to the browser if a test method fails before its own cleanup line runs?
    If `quit()` sits in the test body below the failing assertion it never runs, and the browser and driver process stay alive after the test is reported as failed. Moved into the teardown hook, the same call runs after a passing and a failing test alike, so the session ends either way. That is the main practical reason the hook exists at this seam.
  • Should assertions ever live in the setup hook?
    No. A failure there is reported as a broken fixture rather than a failed test, and it usually marks every test in the class red with the same message, which hides which behaviour actually broke. Keep the hook to constructing the driver, applying options and navigating to the page under test, and put the checks in the test methods.

The setup hook is the wall switch that powers the label printer, not a step in the print job: every job assumes the machine is already on, and the switch is flipped off afterwards whether the job printed or jammed.

saying these in an interview costs you the question

  • Puts driver.quit() as the last line of the test body
  • Copies the driver constructor into every test method
  • Thinks the runner closes the browser when a test ends
  • Writes assertions in the setup hook and reports fixture errors
  • Believes a teardown hook is skipped when the test fails
open as a page

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?

level: middleimportance: should knowfreq 48%

basics

~20 s

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

open as a page

A Selenium suite's failure listener finds a dead session when it tries to capture evidence — why, and where must that listener sit?

level: seniorimportance: nice to knowfreq 34%

basics

~20 s

Because the teardown hook already called quit, which deletes the session. Any command after that throws, so the capture step has to run before the quit, against the driver instance the failing test was actually using.

open as a page