How to Use Selenium with Python and Nose
Use Selenium WebDriver from Python tests, manage browser sessions safely, and understand what Nose compatibility means for a legacy test suite.
Selenium lets Python control a real browser through WebDriver. You can put browser actions and assertions in a Nose test, but Nose is a legacy runner: check your project’s pinned versions and test conventions before depending on it. For a new suite, Selenium’s current examples use Python’s built-in unittest and pytest.
The key lifecycle rule is to close every WebDriver session, even when an assertion fails. The examples below show local Chrome automation, Nose’s command-line runner, and current Selenium patterns you can use when maintaining or replacing a Nose suite.
1. Install Selenium and prepare a browser
Selenium’s Python bindings automate browser interaction through WebDriver. The official Selenium Python API documentation currently describes support for Python 3.10 and later and lists Chrome, Edge, Firefox, Safari, WebKitGTK, and WPEWebKit; confirm the supported Python version and browser for the Selenium release you install. The Selenium downloads page lists Python client 4.49.0 as stable, released September 9, 2026. These version details can change.
python -m venv .venv
# macOS or Linux
. .venv/bin/activate
# Windows PowerShell
# .venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install selenium nose
Modern Selenium versions use Selenium Manager to find or manage a compatible browser driver when you instantiate WebDriver. You generally do not need to download a driver manually and put it on PATH, as older setup instructions often say. You still need a supported browser installed and an environment where Selenium Manager can do its work. Local automation does not require Selenium Server; remote WebDriver sessions use a Selenium Grid.
Reference: Selenium Python API, Selenium Manager, Selenium documentation, and Selenium downloads.
2. Write a browser test that Nose can run
Nose 1.3.7 discovers tests using naming conventions, including files such as test_example.py and functions or methods whose names start with test. This example uses Nose’s with_setup decorator to create and close a browser for a test. Replace the example URL and expected text with a page you control.
# test_example.py
from selenium import webdriver
from selenium.webdriver.common.by import By
from nose.tools import with_setup
driver = None
def setup_browser():
global driver
driver = webdriver.Chrome()
def teardown_browser():
global driver
if driver is not None:
driver.quit()
driver = None
@with_setup(setup_browser, teardown_browser)
def test_homepage_has_heading():
driver.get("https://example.com")
heading = driver.find_element(By.TAG_NAME, "h1")
assert heading.text == "Example Domain"
Run the test from the directory containing the file:
python -m nose -v
Nose also documents invoking its runner from a script with nose.main() or nose.run(). For routine test work, the command-line runner is easier to configure and inspect. See the Nose usage documentation.
Why cleanup matters
driver.quit() ends the WebDriver session and closes its browser windows. If you only close a window, the session or driver process may remain. A test that fails before cleanup can leave orphaned browser processes, consume resources, or interfere with later tests. Use a cleanup hook or fixture teardown that runs regardless of assertion results.
3. Nose compatibility and choosing a runner
Nose usage documentation is useful when maintaining existing suites, but it is not evidence that Nose is currently maintained or a recommended choice for new projects. Selenium’s current Python API examples demonstrate unittest and pytest.
There is also a specific compatibility caveat: the versioned pytest 8.0 documentation describes basic support for tests written for Nose and marks it deprecated, with removal likely in pytest 8.x. It documents support for some Nose idioms, such as setup and teardown hooks and SkipTest, but says plain classes using setUp, tearDown, setUpClass, or tearDownClass are not treated like unittest.TestCase classes. Check the documentation for your exact pytest version and test patterns; compatibility is not universal.
| Situation | Practical next step |
|---|---|
| Your repository already runs Nose | Inventory plugins, setup and teardown patterns, command-line options, and pinned Python and Selenium versions. Run the suite in its pinned environment before changing anything. |
| You want to run existing Nose tests with pytest | Check the exact pytest version’s Nose compatibility notes and identify unsupported idioms. Treat deprecation as a migration concern, not a permanent guarantee. |
| You are starting a new suite | Follow a current Selenium unittest or pytest example and use the runner your project can maintain. |
| You need browsers on another machine | Configure remote WebDriver against a Selenium Grid and account for its browser and network configuration. |
There is no universal runner winner in the cited guidance. The useful decision points are the runner and plugins already in the repository, whether its fixtures and setup conventions are supported, the versions pinned by the project, and whether browsers run locally or through Grid.
References: Selenium guidance on organizing test suites and pytest 8.0 Nose compatibility.
4. Current Selenium patterns with unittest and pytest
If you are moving away from Nose or writing a new suite, these patterns make browser cleanup explicit. They use the same local Chrome setup as the Nose example.
unittest
# test_unittest_example.py
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
class HomepageTests(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.addCleanup(self.driver.quit)
def test_heading(self):
self.driver.get("https://example.com")
heading = self.driver.find_element(By.TAG_NAME, "h1")
self.assertEqual(heading.text, "Example Domain")
if __name__ == "__main__":
unittest.main()
Run it with python -m unittest -v. Registering quit with addCleanup ensures it runs even if later setup or the test fails.
pytest
Install pytest in the same environment, then use a yielding fixture so teardown follows the test:
python -m pip install pytest
# test_pytest_example.py
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
@pytest.fixture
def driver():
browser = webdriver.Chrome()
yield browser
browser.quit()
def test_heading(driver):
driver.get("https://example.com")
heading = driver.find_element(By.TAG_NAME, "h1")
assert heading.text == "Example Domain"
Run it with python -m pytest -v. When fixture setup itself can fail after creating resources, use a fixture structure that registers cleanup immediately after resource creation.
These are documented Selenium-style patterns; this article does not claim to have executed them in your environment. Consult the Selenium WebDriver getting started guide for the current API details.
5. Browser interactions, waits, and remote sessions
After navigation, locate elements with the By strategies and interact through WebDriver. Prefer waiting for the condition you need over fixed sleeps, especially for pages that load asynchronously.
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
# After creating driver:
driver.get("https://example.com")
wait = WebDriverWait(driver, 10)
heading = wait.until(
EC.visibility_of_element_located((By.TAG_NAME, "h1"))
)
assert heading.text == "Example Domain"
The 10-second wait is an example timeout, not a performance claim. Choose a timeout based on the application and CI environment. Avoid mixing implicit and explicit waits without understanding their interaction, since that can make actual wait times difficult to predict.
For a remote session, point a Selenium Remote WebDriver client at the Grid URL configured for your environment:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
driver = webdriver.Remote(
command_executor="http://grid-host:4444",
options=options,
)
try:
driver.get("https://example.com")
print(driver.title)
finally:
driver.quit()
Replace grid-host with the reachable Grid endpoint and use the browser options required by that Grid. The example does not configure authentication, TLS, or Grid node provisioning; those depend on the deployment.
6. Troubleshooting
| Symptom | Likely cause | What to check or change |
|---|---|---|
ModuleNotFoundError: No module named 'selenium' |
The package is missing from the active Python environment. | Activate the intended virtual environment, then run python -m pip install selenium with that interpreter. |
ModuleNotFoundError: No module named 'nose' |
Nose is not installed in the active environment. | For an existing Nose suite, install its pinned dependency with the project interpreter. For a new suite, use the documented Selenium unittest or pytest pattern. |
| Browser or driver cannot be located | The browser is absent or unsupported, Selenium Manager cannot resolve a compatible driver, or the environment blocks required access. | Confirm browser and platform support for your Selenium version, check browser installation, and inspect Selenium Manager output and environment access. Use explicit driver management only when required by your setup. |
| Nose reports no tests | File or test names do not match discovery conventions, or execution starts from the wrong directory. | Use a test_*.py file and test_* function or method, then run Nose from the project root or specify the test path. |
| Test passes locally but fails in CI | Browser availability, headless configuration, display, network access, timing, or environment versions differ. | Compare Python, Selenium, browser, and runner versions; inspect browser startup output; wait for the needed page condition; and verify the CI image has the required browser environment. |
| Element lookup fails intermittently | The element has not appeared or become usable when the lookup runs, or the locator no longer matches. | Confirm the locator against the current page and use an explicit wait for presence, visibility, or clickability as appropriate. |
| Browser processes remain after tests | Cleanup did not run, or the code closed a window rather than quitting the session. | Put driver.quit() in a guaranteed cleanup path, fixture teardown, or finally block. |
| pytest behaves differently from Nose | The suite relies on a Nose idiom that pytest’s versioned compatibility layer does not support, or compatibility has changed. | Read compatibility documentation for the installed pytest version and migrate unsupported setup, teardown, plugins, and options deliberately. |
7. Performance, reliability, and cost
Browser tests are most reliable when each test owns and closes its session, uses explicit waits for application state, and avoids hidden dependencies on test order or shared browser state. Reuse setup only when isolation remains clear; shared state can make failures harder to reproduce. On CI, browser startup and page loading are part of test execution, so keep the suite focused on browser behavior that needs a real browser and use the project’s other test layers for checks that do not.
Selenium itself is an open-source browser automation project; operational costs depend on where browsers run, the machines or Grid capacity you provide, and CI usage. The cited sources provide no benchmark or universal runtime figure. Measure your own suite and environment before deciding how many parallel browser sessions to run. Remote Grid can centralize browser execution, but adds network and Grid availability dependencies.
8. Or skip the browser setup
If the task is to capture a page image or PDF rather than assert on interactive browser behavior, ScreenshotNeo provides a website screenshot API and MCP server for developers. One GET request can return PNG, JPEG, WebP, or PDF; see the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie and consent banners are accepted before capture, and 60+ known consent platforms, newsletter popups, and chat widgets are removed; each step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. The response identifies the page verdict and billing status in headers.
- An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
- 1,000 screenshots per month are free with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.
9. FAQ
Does Nose control the browser?
No. Selenium WebDriver controls the browser; Nose discovers and runs the Python tests containing those browser actions and assertions.
Do I need Selenium Server for a local test?
No. A local WebDriver session does not require Selenium Server. A remote WebDriver session uses Selenium Grid.
Can I keep using an existing Nose suite?
You can maintain it in its current pinned environment, but inspect its plugins and test idioms before upgrading dependencies or moving to pytest. Nose-specific behavior may not map directly to another runner.
Should a screenshot check use Selenium?
Use Selenium when the test needs browser interaction and assertions about behavior. For a page image or PDF without test-runner setup, a screenshot API such as ScreenshotNeo may fit the task.


