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

In a Django browser test, open self.live_server_url, find the anchor with a stable Selenium locator, call .click(), and wait for the resulting URL or page state. Use a maintained browser such as headless Chrome or Firefox: PhantomJS development is suspended, its last stable release was 2.1.1, and current Selenium no longer provides native PhantomJS support.

Recommended Django test pattern

StaticLiveServerTestCase is convenient when your project uses Django’s staticfiles app; otherwise use LiveServerTestCase. The live-server URL points the browser at the test site rather than at a development server you started separately.

  1. Install Django, Selenium, and a supported browser plus its driver. In CI, configure the browser for headless operation.
  2. Put the test in an app’s tests.py or test package and use a live-server test class.
  3. Navigate to self.live_server_url.
  4. Locate the anchor with an ID, test hook, CSS selector, XPath, or link-text strategy.
  5. Wait for the link to be clickable, click it, then wait for a condition that proves navigation completed.

Complete example with headless Chrome

from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class LinkTest(StaticLiveServerTestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        options = Options()
        options.add_argument("--headless=new")
        options.add_argument("--window-size=1440,1000")
        cls.selenium = webdriver.Chrome(options=options)
        cls.selenium.implicitly_wait(5)

    @classmethod
    def tearDownClass(cls):
        cls.selenium.quit()
        super().tearDownClass()

    def test_details_link(self):
        self.selenium.get(f"{self.live_server_url}/")
        wait = WebDriverWait(self.selenium, 10)
        link = wait.until(
            EC.element_to_be_clickable(
                (By.CSS_SELECTOR, "a[data-testid='details']")
            )
        )
        link.click()
        wait.until(EC.url_contains("/details/"))

The selector and URL are illustrative. Replace them with values from your application. If you do not use staticfiles, change the base class and import to django.test.LiveServerTestCase.

Choosing a locator for an anchor

Selenium’s Python API exposes locator strategies through By. Choose the narrowest selector that remains stable when the page’s styling or copy changes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Strategy Example Best use Risk
ID (By.ID, "details-link") A unique, permanent element ID Breaks if IDs are generated or redesigned
CSS (By.CSS_SELECTOR, "a[data-testid='details']") Stable test hooks, classes, attributes, or scoped links An overly broad selector may match the wrong anchor
XPath (By.XPATH, "//main//a[@rel='next']") Relationships or attributes CSS cannot express conveniently Long paths tied to markup structure are fragile
Exact link text (By.LINK_TEXT, "View details") Visible wording is stable and unique Text must match exactly, including wording and spacing
Partial link text (By.PARTIAL_LINK_TEXT, "details") A predictable portion of the visible label Can select the first of several matching links

Exact and partial text

exact = self.selenium.find_element(By.LINK_TEXT, "View details")
exact.click()

partial = self.selenium.find_element(By.PARTIAL_LINK_TEXT, "details")
partial.click()

Use text locators only when the rendered label is intentionally part of the contract. Localization, punctuation, responsive labels, or an added navigation link can make them fail or become ambiguous. A dedicated id or data-testid is usually clearer for a functional test.

Scope repeated links

card_link = self.selenium.find_element(
    By.CSS_SELECTOR,
    "article[data-testid='product-card'] a[data-testid='details']"
)
card_link.click()

Scoping prevents a header, footer, or another card from satisfying the same selector. If several matches are expected, use find_elements and assert the count before choosing an index; do not silently click whichever element happens to come first.

Waiting correctly after a click

A click returning does not prove that Django has rendered the next state. Modern pages can update the DOM asynchronously, and Django notes that browser navigation and database activity can overlap on the live server. This is particularly important with an in-memory SQLite database, where the browser thread and test thread can share a connection.

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

Wait for URL navigation

link.click()
WebDriverWait(self.selenium, 10).until(
    EC.url_contains("/details/")
)

Wait for a destination element

link.click()
WebDriverWait(self.selenium, 10).until(
    EC.visibility_of_element_located(
        (By.CSS_SELECTOR, "main[data-testid='details-page']")
    )
)

Wait for a state change

link.click()
WebDriverWait(self.selenium, 10).until(
    EC.text_to_be_present_in_element(
        (By.CSS_SELECTOR, "h1"), "Details"
    )
)

Prefer a condition that expresses the user-visible outcome: a URL fragment, a heading, a success message, a changed button state, or a loaded component. A fixed sleep merely delays the test and can still be too short or unnecessarily slow.

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.

Clicking links that need preparation

Scroll an off-screen link into view

link = WebDriverWait(self.selenium, 10).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, "a[data-testid='details']"))
)
self.selenium.execute_script(
    "arguments[0].scrollIntoView({block: 'center'});", link
)
WebDriverWait(self.selenium, 10).until(
    EC.element_to_be_clickable((By.CSS_SELECTOR, "a[data-testid='details']"))
).click()

Dismiss an overlay

dismiss = WebDriverWait(self.selenium, 5).until(
    EC.element_to_be_clickable((By.ID, "close-dialog"))
)
dismiss.click()
WebDriverWait(self.selenium, 10).until(
    EC.element_to_be_clickable((By.CSS_SELECTOR, "a[data-testid='details']"))
).click()

Do not use JavaScript to invoke click() as the default workaround. A real WebDriver click checks visibility, hit-testing, and interactability in ways that more closely represent a user action. Use JavaScript only when the application deliberately relies on behavior that WebDriver cannot trigger, and document why.

PhantomJS: legacy suites and migration

PhantomJS is a historical headless browser. Its official site says, “Important: PhantomJS development is suspended until further notice.” The project’s archival issue says it was going to be archived because of a lack of active contribution and records version 2.1.1 as the last known stable release. Selenium removed native PhantomJS support because its WebDriver implementation was no longer actively developed and directed users toward Chrome or Firefox headless mode.

That means a new Django test should not be designed around PhantomJS. Existing suites may still be pinned to an old Selenium release and a locally installed PhantomJS binary, but this combination carries compatibility and security-maintenance costs. Plan a browser migration rather than adding more tests to it.

What a historical PhantomJS setup looked like

# Legacy-only example; do not use for new projects
from selenium import webdriver

driver = webdriver.PhantomJS()
driver.get("http://localhost:8000/")
driver.find_element_by_link_text("View details").click()

The older find_element_by_... methods and the PhantomJS driver are not the current Selenium interface. In maintained code, use find_element(By.LINK_TEXT, ...) (or another explicit strategy) and Chrome or Firefox.

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

Running the Django test reliably

Command and isolation

python manage.py test your_app.tests.LinkTest

Run the test with the same settings and environment variables used by CI. Create required records in setUp or a test factory, and use URLs generated by Django’s URL helpers where possible. Avoid depending on data from a separately running development server.

Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

Headless CI considerations

  • Install matching browser and driver versions, or use an environment that manages the pairing.
  • Set a deterministic viewport so responsive layouts do not hide or replace the link.
  • Capture the current URL and a screenshot when a wait times out; the evidence usually reveals a redirect, overlay, validation error, or wrong selector.
  • Keep explicit waits close to the action they protect. Excessive implicit waits combined with explicit waits can make failures slow and difficult to diagnose.

Troubleshooting click failures

Symptom Likely cause Fix
NoSuchElementException Wrong route, selector, frame, or render timing Assert the starting URL, inspect the DOM, wait for presence, and switch into the correct iframe if applicable.
ElementNotInteractableException Hidden, disabled, or zero-size link Wait for visibility and clickability; remove the obstructing state in the application or test setup.
ElementClickInterceptedException Cookie dialog, modal, sticky header, or chat widget covers the anchor Close the overlay, scroll the link to a safe position, then wait and click again.
Click returns but URL never changes The link updates content with JavaScript, opens a new tab, or navigation failed Wait for the destination element or URL, inspect window handles, and assert the application’s intended state rather than assuming a full navigation.
Intermittent database or page failures Race between live-server requests and test data, especially with in-memory SQLite Wait for a concrete page condition, avoid shared mutable state, and use a database configuration appropriate for concurrent test access.
Text locator stops working after a copy change Exact visible text is not a stable contract Add a semantic ID or data-testid, or use a scoped attribute selector.
PhantomJS session will not start Unsupported or unmaintained driver/browser combination Migrate to headless Chrome or Firefox and update to the current Selenium API.
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 clean image or PDF of a page rather than an interaction assertion, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. 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. AI clients such as Claude and Cursor can use its MCP tools take_screenshot, get_page_info, and capture_pdf.

Use the ScreenshotNeo API for a one-call capture:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo also supports full-page and selector captures, device presets and custom viewports, retina scale, dark mode, PDF options, custom CSS and JavaScript, pre-capture clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, and a usage API. Every feature is on every plan. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots.

Start with 1,000 free screenshots a month—no card required.

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

Frequently Asked Questions

Should I use LINK_TEXT or CSS for a translated link?

Use a stable ID, data-testid, or semantic attribute. Exact link text is tied to the rendered language and must match exactly.

Can Selenium click a link that opens a new tab?

Yes. Record the original window handle, click, wait for a second handle, switch to it, and then assert the new tab’s URL or content.

Is PhantomJS compatible with current Django?

Django itself is not the deciding compatibility layer; the problem is the unmaintained PhantomJS WebDriver and old Selenium bindings. Use maintained Chrome or Firefox drivers for new work.

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.

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