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

PhantomJS is deprecated. Selenium’s Python changelog says, “PhantomJS is now deprecated, please use either Chrome or Firefox in headless mode.” Replace the PhantomJS driver, then synchronize each login action with the state your application actually reaches. A browser reaching a document-ready state does not mean JavaScript has finished rendering, redirecting, or establishing a session.

This guide shows a maintainable Python migration, explains why “Selenium login script not working” failures occur, and gives a decision path for browser-based login tests versus API-based test-state setup. Use only accounts and systems you are authorized to test.

1. Confirm what is failing before changing selectors

Record the Python version, Selenium version, browser and version, operating system, driver information, proxy settings, and the complete exception traceback. Save browser/driver logs in CI. First classify the failure:

  • Startup: the browser or driver cannot launch.
  • Navigation: DNS, proxy, TLS, certificate, timeout, or blocked-resource problems prevent the page from loading.
  • Flow: a locator no longer matches, a button is covered, a redirect is unexpected, or a consent/MFA step appears.
  • Authentication: the application rejects credentials, requires an extra factor, or detects an unauthorized automation attempt.

PhantomJS’s legacy troubleshooting material lists network requests, TLS/SSL, proxies, JavaScript errors, exceptions, and resource logging as diagnostic areas; those categories are still useful for identifying the layer that failed, but they are not a reason to keep PhantomJS in a new implementation. See the Selenium Python changelog.

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

2. Replace PhantomJS with a supported browser

Do not translate an old webdriver.PhantomJS() constructor and hope it remains reliable. Use the current Selenium browser options API. Selenium Manager can obtain a compatible driver when your environment permits it; otherwise install and manage a driver that matches the browser supplied by your CI image.

Headless Chrome

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--headless")
options.add_argument("--window-size=1365,900")
# Add --no-sandbox or --disable-dev-shm-usage only when your container requires them.
driver = webdriver.Chrome(options=options)

try:
    driver.get("https://example.com/login")
    print(driver.title)
finally:
    driver.quit()

Headless Firefox

from selenium import webdriver
from selenium.webdriver.firefox.options import Options

options = Options()
options.add_argument("-headless")
driver = webdriver.Firefox(options=options)

try:
    driver.get("https://example.com/login")
    print(driver.current_url)
finally:
    driver.quit()

Chrome and Firefox are the alternatives named in Selenium’s deprecation notice. Select the browser that matches the production coverage you need and that your CI image and driver-management policy support; the available sources do not establish a universal winner. Selenium’s current browser-options documentation describes configuration and driver management. Run visibly once during migration whenever possible: a headed browser makes redirects, overlays, consent dialogs, and MFA prompts obvious.

3. Build the login flow around explicit state

Navigation completion is not application readiness. A page can report its configured readiness state while JavaScript is still replacing fields, enabling a button, or redirecting after authentication. Selenium’s waiting guidance recommends waiting for the condition required by the next action. Keep implicit wait at its default when using explicit waits; Selenium warns, “Do not mix implicit and explicit waits.”

A complete explicit-wait example

The selectors below are examples only. Inspect the target site and replace them with its actual identifiers. Prefer stable IDs, names, or data attributes over brittle class chains.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import os
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
from selenium.common.exceptions import TimeoutException

LOGIN_URL = "https://example.com/login"
POST_LOGIN_URL_PART = "/account"

options = Options()
options.add_argument("--headless")
options.add_argument("--window-size=1365,900")
driver = webdriver.Chrome(options=options)
wait = WebDriverWait(driver, 20)

try:
    driver.get(LOGIN_URL)

    username = wait.until(EC.visibility_of_element_located((By.NAME, "username")))
    password = wait.until(EC.visibility_of_element_located((By.NAME, "password")))
    username.clear()
    username.send_keys(os.environ["TEST_USERNAME"])
    password.clear()
    password.send_keys(os.environ["TEST_PASSWORD"])

    submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "button[type='submit']")))
    submit.click()

    # Choose a signal that proves this application considers login complete.
    wait.until(EC.url_contains(POST_LOGIN_URL_PART))
    wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "[data-testid='account-menu']")))
    print("Logged in:", driver.current_url)
except TimeoutException:
    print("Timed out at", driver.current_url)
    driver.save_screenshot("login-timeout.png")
    raise
finally:
    driver.quit()

Choose the right condition

  • presence_of_element_located means the element exists in the DOM, even if hidden.
  • visibility_of_element_located means it exists and is visible.
  • element_to_be_clickable checks visibility and enabled state, but an overlay can still intercept the click.
  • url_contains or url_matches verifies a redirect when the application uses predictable URLs.
  • staleness_of is useful when submission replaces the old form.

Wait for a post-login heading, account menu, API-backed dashboard element, or redirect that is meaningful for this site. Do not make a fixed time.sleep() the primary synchronization mechanism: it is either unnecessarily slow or still too short on a busy runner.

4. Make the flow observable and site-specific

Run headed first

Temporarily remove the headless argument and slow the flow with deliberate inspection, not production sleeps. Verify the initial URL, field names, button behavior, redirects, and whether a consent banner or MFA challenge interrupts the sequence. Headless and headed modes can differ in viewport, GPU behavior, permissions, and timing, so set an explicit window size.

Handle consent, MFA, and bot checks deliberately

There is no universal selector for these steps. Use the site’s documented test account and test hooks where available. If MFA is part of the login behavior under test, model that step explicitly; if policy forbids automated MFA or a CAPTCHA appears, stop and use an approved test environment rather than trying to bypass the control.

Capture evidence at the failure point

driver.save_screenshot("failure.png")
with open("failure.html", "w", encoding="utf-8") as file:
    file.write(driver.page_source)
print(driver.current_url)
print(driver.title)

Compare the saved HTML with the locator you expect. A successful HTTP response can still contain an error page, a login challenge, or a JavaScript exception.

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

5. Decide whether login belongs in this test

Selenium distinguishes testing the login experience from preparing an authenticated state for another feature. Its “Generating application state” guidance says, “A method should be created to gain access to the AUT* (e.g. using an API to login and set a cookie).”

Approach Use it when What it covers Trade-off
Browser-driven login The login form, validation, redirect, or MFA is the behavior under test Real UI, browser cookies, JavaScript, and navigation More timing, UI, browser, and account dependencies
API login plus cookie The test concerns an already-authenticated feature Authenticated application state Does not validate the login interface

The API route is usually shorter and less fragile for every test that does not need to exercise the login page. Obtain a test session through the application’s supported API, then add the returned cookie before navigating to the protected page. Cookie names, domains, paths, CSRF requirements, and token exchange are application-specific; follow that application’s documentation rather than copying a generic cookie snippet.

6. Troubleshoot the remaining “login script not working” cases

“Unable to obtain driver” or browser exits immediately

Check the browser binary, permissions, container libraries, and driver compatibility. Print versions and run the same image locally. Let Selenium Manager resolve the driver or pin a compatible driver in CI; do not mix an old PhantomJS executable with a new Selenium API.

Timeout waiting for a field

Confirm the URL and inspect page_source. The form may be inside an iframe, rendered only after JavaScript, renamed, or replaced by a consent screen. If it is in an iframe, wait for and switch to the correct frame before locating fields, then switch back with driver.switch_to.default_content().

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

Click intercepted or element not interactable

Wait for visibility and clickability, scroll the element into view, and identify overlays. Fix the application state or close an authorized consent dialog instead of forcing JavaScript clicks that bypass the user path.

Redirect occurs but the assertion fails

Log the final URL and inspect redirects. The application may use a different host, trailing slash, locale, or intermediate security page. Wait for a stable post-login element in addition to, or instead of, a URL fragment.

TLS, proxy, or resource errors

Verify the runner’s certificate store, proxy environment variables, DNS, firewall, and clock. Test the same URL from the runner outside Selenium. Avoid disabling certificate validation in production-like tests unless an approved local test certificate requires it.

Credentials are rejected

Check the account’s state, environment, required fields, CSRF token, lockout policy, and whether MFA is mandatory. Never print passwords or session tokens in logs.

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

7. Reliability and maintenance checklist

  • Pin or deliberately update Python, Selenium, browser, and driver versions together.
  • Use environment variables or a secret manager for credentials.
  • Keep selectors in one place and favor stable test attributes.
  • Use explicit waits tied to application state; leave implicit wait at its default.
  • Save a screenshot, HTML, URL, and browser log on failure.
  • Use a dedicated test account and reset its state between runs.
  • Run headed during diagnosis, then validate headless in the same CI image.
  • Retry only known transient infrastructure failures; do not hide deterministic locator or authentication defects with retries.
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 to capture a page rather than test its login interaction, 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/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing result. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—work with Claude, Cursor, and other MCP clients.

One request returns PNG, JPEG, WebP, or PDF. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. See the ScreenshotNeo API documentation for options such as full-page capture, CSS selectors, custom waits, headers, cookies, user agents, geolocation, blocking, signed links, async jobs, and bulk 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}`);

Create a free ScreenshotNeo account to use the 1,000 monthly screenshots without a card.

Frequently Asked Questions

Can I keep PhantomJS temporarily?

It may help reproduce a legacy failure, but Selenium has deprecated it. Migrate the maintained script to headless Chrome or Firefox rather than investing in PhantomJS workarounds.

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

Why does document-ready not prove that login finished?

Client-side JavaScript can still render fields, submit requests, replace the form, or redirect after the document reaches its configured readiness state. Wait for the post-login state your test needs.

Should every Selenium test log in through the UI?

No. Drive the browser when login itself is under test; otherwise prepare authenticated state through the application’s supported API and cookie mechanism.

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.