Driver Lifecycle and Navigation
What a WebDriver instance actually represents, structuring its lifecycle with JUnit annotations, and the navigation methods beyond a simple get().
What you'll learn
- Structure driver creation and teardown using JUnit's @BeforeEach/@AfterEach lifecycle
- Use WebDriver's navigation methods (get, back, forward, refresh) correctly
- Explain why a shared driver instance across tests risks state leaking between them
Prerequisites
Explanation
A WebDriver instance represents one real, running browser session — created (new ChromeDriver()), used for a series of commands, then closed (driver.quit()). The previous lesson's guided local lab used a manual try/finally inside one test method; a real, multi-test suite structures this with JUnit lifecycle annotations instead: @BeforeEach void setup() { driver = new ChromeDriver(); } creates a fresh driver before every single test method, and @AfterEach void teardown() { driver.quit(); } guarantees cleanup after every one, regardless of that test's outcome — JUnit runs @AfterEach even when a test method throws, giving you the same guaranteed-cleanup property as finally, but applied automatically across an entire test class rather than written by hand in each method.
This per-test fresh driver pattern matters for the same fundamental reason Playwright's per-test context isolation matters (if you've taken that course): a shared driver instance reused across multiple tests can leak state — cookies, browser history, whatever page the previous test happened to leave the browser on — silently affecting a later test's starting conditions in ways that make failures hard to reproduce and diagnose. The cost is real (launching a fresh browser per test is slower than reusing one), but the reliability and diagnosability payoff is why it's the standard, recommended default for correctness-focused test suites, not merely a performance-agnostic style preference.
Navigation goes beyond driver.get(url) (load a URL, waiting for the document.readyState to reach "complete" by default): driver.navigate().to(url) is functionally equivalent to get(); driver.navigate().back() and .forward() move through the browser's real history stack, exactly like clicking a real back/forward button; driver.navigate().refresh() reloads the current page. These matter specifically for testing workflows that depend on real browser history behavior — confirming a "back" button correctly returns a user to a previous, valid state, rather than an error page or a broken partial render, is a genuine, common test scenario these methods exist to support.
Example
Modeling the per-test-fresh-driver lifecycle and navigation-history stack, without a real browser.
class FakeDriverSession {
constructor() { this.history = []; this.historyIndex = -1; this.cookies = new Set(); }
get(url) {
this.history = this.history.slice(0, this.historyIndex + 1);
this.history.push(url);
this.historyIndex++;
}
back() { if (this.historyIndex > 0) this.historyIndex--; }
forward() { if (this.historyIndex < this.history.length - 1) this.historyIndex++; }
currentUrl() { return this.history[this.historyIndex]; }
}
// beforeEach: a FRESH session per test -- no leaked cookies/history from a previous test.
function runTest(testBody) {
const driver = new FakeDriverSession(); // fresh every time
testBody(driver);
// afterEach equivalent: driver discarded here, nothing carries over
}
runTest((driver) => {
driver.get("/page-a");
driver.get("/page-b");
driver.back();
console.log(driver.currentUrl()); // "/page-a" -- real history navigation
});Try it yourself
Call driver.forward() after the back() call above and confirm it returns to /page-b.
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Guided exercise
Guided exercise
Write freshDriverPerTest(testNames, runTest) modeling JUnit's @BeforeEach/@AfterEach: for EACH name in testNames, create a fresh 'driver' object ({cookies: new Set()}), call runTest(driver, name), then discard it. Return an array of booleans: true if that test's driver had ZERO cookies at the start (proving no leakage from a previous test).
Checks: every test starts with a genuinely fresh, cookie-free driver · handles an empty test-names list
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Independent exercise
Independent exercise
Write navigationHistory(actions) where actions is an array of {type: 'get'|'back'|'forward', url?} objects. Simulate them in order against a history stack (starting empty, index -1) and return the final current URL (or null if history is empty).
Checks: back correctly returns to a previous page · forward correctly returns to a page navigated back from · handles no actions at all · a new navigation after going back truncates any forward history
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Common mistakes
- Sharing one WebDriver instance across multiple tests to save time -- this risks leaked cookies, history, or page state silently affecting a later test's starting conditions, making failures hard to reproduce.
- Putting driver.quit() inside @Test methods individually instead of a shared @AfterEach -- this duplicates cleanup logic across every test and risks a missed cleanup if any single test forgets it.
- Assuming driver.navigate().back() is purely cosmetic -- it exercises the browser's REAL history mechanism, which is exactly what makes it useful for testing that a back button leads to a valid, correctly-rendered state, not an error.
Knowledge check
Takeaway
Structure driver creation/teardown with JUnit's @BeforeEach/@AfterEach for guaranteed, automatic cleanup across every test in a class — a fresh driver per test avoids state leaking between tests, and navigation methods beyond get() exercise the browser's real history mechanism.
Summary
@BeforeEach creates a fresh WebDriver before every test; @AfterEach guarantees driver.quit() runs after every test regardless of outcome. A shared driver across tests risks leaked state affecting later tests. navigate().back()/forward()/refresh() exercise the browser's real history stack, not a simulated re-fetch.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.