Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

If Python raises InvalidSessionIdException at driver.quit(), the browser session is usually already gone. A previous quit(), closing the last tab with close(), a fixture that cleaned up first, or a browser crash can delete the session; the later cleanup call merely exposes that fact. Read the first exception in the traceback, give one piece of code ownership of the driver, and put one unconditional quit() in a finally block or a Selenium context manager.

What quit() actually does

WebDriver has two different lifecycle operations:

Call Scope What remains
driver.close() Closes the current tab or window. The session and other windows remain only if at least one valid window is still open.
driver.quit() Deletes the entire WebDriver session. No WebDriver windows, browser process, or driver process remain.

Selenium recommends quit() when the browser session is finished. It closes every window and tab associated with the session, terminates the browser and background driver processes, and tells Selenium Grid that the slot is available again. Calling close() immediately before quit() is therefore unnecessary for normal teardown.

Why the error appears on the quit() line

The session was already quit

InvalidSessionIdException is raised when the server no longer has the supplied session identifier. A helper, fixture finalizer, context manager, or an earlier error path may already have called quit(). The second call cannot operate on a session that has been deleted.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The last window was closed

If only one tab or window existed, driver.close() can close the last browsing context and implicitly delete the session. Any later command—including quit(), current_url, or get()—then targets an invalid identifier. With multiple windows, switch to a still-valid handle after closing one:

from selenium import webdriver


driver = webdriver.Chrome()
driver.get("https://example.com")

first = driver.current_window_handle
driver.switch_to.new_window("tab")
second = driver.current_window_handle

driver.switch_to.window(first)
driver.close()                 # second tab is still alive
driver.switch_to.window(second)
print(driver.title)
driver.quit()                   # final owner ends the session

A browser or driver process died

A crash, operating-system termination, or a remote Grid node failure can make the server forget the session. The cleanup line may be the first command that contacts the missing session, but it is not the original failure.

Two pieces of test code share one driver

Parallel tests or nested fixtures can accidentally use the same driver. One test quits it while another is still running. Each test should normally create and own its own driver, or a clearly defined fixture should be the sole owner and sole teardown location.

Find the first failure before changing teardown

  1. Read the traceback from the top and identify the earliest exception. A teardown error at the bottom can mask a navigation, assertion, browser-crash, or timeout failure that happened first.
  2. Search the test, fixtures, helper functions, and context-manager scopes for every close(), quit(), browser-kill operation, and teardown callback.
  3. Write down the owner of the driver. Exactly one owner should perform final cleanup.
  4. Check whether the last window was closed before the failing command. Record window handles when debugging multi-tab flows.
  5. Separate lifecycle errors from startup errors. A session that never starts cannot be repaired by changing quit().

Reliable cleanup patterns in Python

try/finally for explicit ownership

Create the driver before entering the protected work, then quit it in finally. The block runs whether browser work succeeds or raises an exception.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from selenium import webdriver


def test_home_page():
    driver = webdriver.Chrome()
    try:
        driver.get("https://example.com")
        assert "Example" in driver.title
    finally:
        driver.quit()

Do not put a second teardown in a helper called by this function. If a helper must accept a driver, it should use it but not own its lifetime.

The Selenium context manager

Python Selenium drivers support with. Leaving the indented block automatically quits the driver, including when an exception escapes.

from selenium import webdriver

with webdriver.Firefox() as driver:
    driver.get("https://example.com")
    print(driver.title)

This is often the safest pattern for a short script. Do not call quit() again after the block; the context manager has already performed cleanup.

Fixture ownership

In a test fixture, choose one model: the fixture yields a driver and quits it after the yield, or the test creates and quits its own driver. Avoid both. A fixture-owned example has one teardown path:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import pytest
from selenium import webdriver


@pytest.fixture
def driver():
    browser = webdriver.Chrome()
    try:
        yield browser
    finally:
        browser.quit()


def test_title(driver):
    driver.get("https://example.com")
    assert "Example" in driver.title

Never share that fixture’s driver across concurrently running tests unless your test runner and synchronization design explicitly guarantee that no test can quit it while another uses it.

Use close() only when you intend to keep the session

close() is appropriate for dismissing one of several tabs while continuing in another. Capture or inspect handles before closing, and switch immediately to a handle that still exists. If the current window is the only one, use quit() instead of close() when you are finished.

handles = driver.window_handles
if len(handles) > 1:
    current = driver.current_window_handle
    driver.close()
    remaining = [h for h in handles if h != current]
    driver.switch_to.window(remaining[0])
else:
    driver.quit()

The list is a snapshot; a page can open or close windows between commands. For highly dynamic applications, refresh window_handles after each window operation and handle a missing handle as a normal lifecycle branch rather than issuing another command on a deleted session.

Distinguish teardown errors from startup errors

InvalidSessionIdException

The server does not recognize the session ID. Investigate an earlier quit(), closing the last window, a browser crash, or competing teardown. Recreating a driver can recover a deliberate restart, but it will not fix the underlying ownership bug.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

SessionNotCreatedException

This is a session-creation problem, not evidence that quit() is wrong. Check that the browser and driver versions are compatible, the driver executable is available and executable, and operating-system policy has not blocked it. Also confirm that the browser can launch under the same account and environment as the test process.

Executable and permission failures

A missing executable, an incorrect path, or a file without execute permission prevents startup. Fix the installation or path first; there is no live session for finally to close.

Timeouts and page failures

A navigation timeout or application exception should still flow through finally. Preserve the original traceback while making teardown defensive in your test framework. Do not catch every exception and silently continue, because that can turn the first useful error into a misleading invalid-session report.

A practical debugging checklist

  • Print the driver owner and creation site while diagnosing parallel or nested tests.
  • Log each lifecycle operation: creation, close(), and quit().
  • Log driver.window_handles before and after tab operations.
  • Remove redundant close() calls from final teardown.
  • Use a context manager or one finally block, not several independent cleanup paths.
  • Run the test serially to detect accidental shared-driver ownership.
  • Check browser, driver, executable, and permission issues when the exception is SessionNotCreatedException.
  • Keep the first exception’s traceback; treat a later teardown exception as secondary unless evidence shows otherwise.

Common scenarios and the correct fix

Symptom Likely cause Fix
quit() raises invalid session after a helper ran The helper already owns and quit the driver. Choose one owner; make the helper borrow the driver or move teardown into the helper.
Error follows close() on a single-tab test The last window deletion removed the session. Use quit() for final cleanup and do not issue further commands.
Only parallel runs fail Tests share a driver or fixture teardown races with browser work. Use one isolated driver per test or synchronize a deliberate shared fixture.
Driver never starts Browser/driver mismatch, missing executable, or permissions. Repair the environment; changing teardown cannot create a session.
Original assertion is hidden by teardown output Cleanup raised a second exception. Inspect the first traceback and keep cleanup in a single, predictable path.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is a static image or PDF rather than interactive WebDriver behavior, ScreenshotNeo provides a single HTTP request. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. It also offers an MCP server for Claude, Cursor, and other MCP clients with take_screenshot, get_page_info, and capture_pdf tools.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

See the ScreenshotNeo API documentation for parameters and response details. The service includes full-page and element capture, lazy-image loading, device and viewport settings, retina scale, dark mode, PDF controls, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed links, asynchronous webhooks, bulk capture for up to 100 URLs per call, a usage API, and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration.

The Free plan includes 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is available on every plan, and yearly billing provides two months free. Create a free ScreenshotNeo account to try it without a card.

FAQ

Can I safely call quit() in an exception handler?

Yes, when that handler is the sole owner and the driver was successfully created. Put normal browser work in try and cleanup in finally; do not add another independent teardown elsewhere.

Should I recreate a driver after an invalid session?

Only if your application intentionally starts a new browser session. First identify why the original disappeared; otherwise recreation can hide a double-quit, last-window, or parallel-ownership bug.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does close() ever end a session?

Yes. Closing the final tab or window can implicitly delete the session, so later commands using that driver can raise an invalid-session error.

Frequently Asked Questions

Can I safely call quit() in an exception handler?

Yes, when that handler is the sole owner and the driver was successfully created. Put normal browser work in try and cleanup in finally; do not add another independent teardown elsewhere.

Should I recreate a driver after an invalid session?

Only if your application intentionally starts a new browser session. First identify why the original disappeared; otherwise recreation can hide a double-quit, last-window, or parallel-ownership bug.

Does close() ever end a session?

Yes. Closing the final tab or window can implicitly delete the session, so later commands using that driver can raise an invalid-session error.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.