0%
Selenium with Python: Automating the Browser

Selenium with Python: Automating the Browser

Learn how to automate the browser with Selenium 4 and Python: installation, locating elements, explicit waits, headless mode, and the Page Object pattern.

I

InSkillCoach

· min

Selenium with Python: Automating the Browser

Selenium is the go-to library for controlling a web browser from a program. Whether you want to automate UI tests, fill out repetitive forms, or extract data from JavaScript-generated pages, Selenium gives you complete control over Chrome, Firefox, or Edge. This guide covers the essentials of Selenium 4 with Python: from installation to the Page Object pattern, including the explicit waits that make the difference between a fragile script and a reliable one.

Installation

Since Selenium 4.6, driver management is automatic thanks to Selenium Manager, which is built into the library. There’s no need to manually download chromedriver or geckodriver: Selenium detects your browser and fetches the matching driver.

# Installation de Selenium 4
pip install selenium

# Vérification de la version installée
pip show selenium

Just make sure a browser is installed on the machine (Chrome, Firefox, or Edge).

First Script

Here is a minimal script that opens a page, reads its title, and closes the browser:

from selenium import webdriver

# Selenium Manager télécharge automatiquement le driver adapté
driver = webdriver.Chrome()

# Navigation vers une page
driver.get("https://www.python.org")

# Lecture du titre de la page
print(driver.title)
# Welcome to Python.org

# Lecture de l'URL courante
print(driver.current_url)
# https://www.python.org/

# Fermeture propre du navigateur
driver.quit()

There are two ways to close the browser: driver.close() closes the current tab, while driver.quit() closes all windows and terminates the driver process. Always prefer quit() at the end of a script.

Locating Elements

Every interaction starts with locating an element in the DOM. Selenium 4 uses the By class combined with find_element (first matching element) or find_elements (list of all matching elements).

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.python.org")

# Par identifiant
barre_recherche = driver.find_element(By.ID, "id-search-field")

# Par sélecteur CSS
bouton = driver.find_element(By.CSS_SELECTOR, "button.search-button")

# Par XPath
lien = driver.find_element(By.XPATH, "//a[contains(text(), 'Downloads')]")

# Tous les liens de la page
liens = driver.find_elements(By.TAG_NAME, "a")
print(f"Nombre de liens : {len(liens)}")

driver.quit()

Locator Strategy Table

StrategyExampleWhen to use it
By.IDBy.ID, "email"Always the first choice if a stable id exists
By.NAMEBy.NAME, "username"Classic forms
By.CSS_SELECTORBy.CSS_SELECTOR, "div.card > a"Versatile, fast, readable
By.XPATHBy.XPATH, "//button[text()='OK']"Searching by text or traversing up the DOM
By.CLASS_NAMEBy.CLASS_NAME, "btn-primary"A single class, no spaces
By.TAG_NAMEBy.TAG_NAME, "table"Collections of elements
By.LINK_TEXTBy.LINK_TEXT, "Contact"Links whose exact text is known
By.PARTIAL_LINK_TEXTBy.PARTIAL_LINK_TEXT, "Cont"Links whose text varies slightly

As a general rule: favor By.ID, then CSS selectors. Reserve XPath for cases CSS can’t handle (selecting by text, navigating to a parent).

Interacting with Elements

Clicking and Typing Text

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

driver = webdriver.Chrome()
driver.get("https://www.python.org")

champ = driver.find_element(By.ID, "id-search-field")

# Saisie de texte
champ.send_keys("selenium")

# Validation avec la touche Entrée
champ.send_keys(Keys.RETURN)

# Effacer un champ avant une nouvelle saisie
champ = driver.find_element(By.ID, "id-search-field")
champ.clear()
champ.send_keys("nouvelle recherche")

driver.quit()

For <select> elements, Selenium provides the Select utility class:

from selenium.webdriver.support.ui import Select

element_select = driver.find_element(By.ID, "pays")
select = Select(element_select)

# Trois façons de sélectionner une option
select.select_by_visible_text("France")
select.select_by_value("fr")
select.select_by_index(2)

# Lire l'option sélectionnée
print(select.first_selected_option.text)

Reading Element Properties

element = driver.find_element(By.CSS_SELECTOR, "h1")

print(element.text)                        # Texte visible
print(element.get_attribute("class"))      # Valeur d'un attribut
print(element.is_displayed())              # True si visible
print(element.is_enabled())                # True si actif

Waits: Implicit vs. Explicit

This is the most important section of this guide. Modern pages load their content asynchronously with JavaScript: an element may not exist yet when your script looks for it. Without a wait, you get random NoSuchElementException errors.

Implicit Wait

The implicit wait applies globally: on every find_element call, Selenium retries for the specified duration before raising an exception.

driver = webdriver.Chrome()

# Attendre jusqu'à 10 secondes pour chaque recherche d'élément
driver.implicitly_wait(10)

It’s simple, but insufficient: the implicit wait only checks presence in the DOM, not visibility or clickability. An element can exist but be hidden or disabled.

The explicit wait targets a specific condition on a specific element, using WebDriverWait and the expected_conditions module:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Chrome()
driver.get("https://www.python.org")

# Attendre jusqu'à 10 secondes que l'élément soit cliquable
wait = WebDriverWait(driver, 10)
bouton = wait.until(
    EC.element_to_be_clickable((By.CSS_SELECTOR, "button.search-button"))
)
bouton.click()

driver.quit()

The most useful conditions:

ConditionChecks that
presence_of_element_locatedThe element exists in the DOM
visibility_of_element_locatedThe element exists and is visible
element_to_be_clickableThe element is visible and enabled
text_to_be_present_in_elementA given text is displayed
invisibility_of_element_locatedAn element (such as a spinner) has disappeared
title_containsThe page title contains a string

Golden rule: never use time.sleep() to wait for an element. A sleep that’s too short makes the script unstable, and one that’s too long slows it down needlessly. An explicit wait stops as soon as the condition is met. Also avoid mixing implicit and explicit waits: the wait times can add up in unpredictable ways.

Headless Mode

Headless mode runs the browser without a graphical interface. It’s essential on a continuous integration server:

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

options = Options()
options.add_argument("--headless=new")       # Nouveau mode headless de Chrome
options.add_argument("--window-size=1920,1080")  # Taille de fenêtre fixe

driver = webdriver.Chrome(options=options)
driver.get("https://www.python.org")
print(driver.title)
# Welcome to Python.org

driver.quit()

Always set the window size in headless mode: by default it’s small, and some elements may be hidden by a responsive layout.

Screenshots

Screenshots are valuable for diagnosing test failures:

# Capture de la page visible
driver.save_screenshot("page.png")

# Capture d'un élément précis
element = driver.find_element(By.ID, "content")
element.screenshot("element.png")

Best practice: systematically capture an image in the except block of your tests to see the state of the page at the moment of failure.

Iframes and Multiple Windows

Iframes

An element located inside an <iframe> is invisible to Selenium until you switch context:

# Basculer vers l'iframe (par élément, nom ou index)
iframe = driver.find_element(By.CSS_SELECTOR, "iframe#editeur")
driver.switch_to.frame(iframe)

# Interagir avec le contenu de l'iframe
driver.find_element(By.TAG_NAME, "body").send_keys("Bonjour")

# Revenir au document principal
driver.switch_to.default_content()

Windows and Tabs

Each window has an identifier (handle). To follow a link that opens a new tab:

fenetre_principale = driver.current_window_handle

# Le clic ouvre un nouvel onglet
driver.find_element(By.LINK_TEXT, "Documentation").click()

# Basculer vers le nouvel onglet
for handle in driver.window_handles:
    if handle != fenetre_principale:
        driver.switch_to.window(handle)
        break

print(driver.title)  # Titre du nouvel onglet

# Fermer l'onglet et revenir à la fenêtre principale
driver.close()
driver.switch_to.window(fenetre_principale)

The Page Object Pattern

As scripts grow, selectors get repeated everywhere and every UI change breaks dozens of lines. The Page Object pattern solves this problem: each page of the site is represented by a class that encapsulates its selectors and actions.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


class PageConnexion:
    """Encapsule la page de connexion : sélecteurs et actions."""

    URL = "https://exemple.com/connexion"
    CHAMP_EMAIL = (By.ID, "email")
    CHAMP_MOT_DE_PASSE = (By.ID, "password")
    BOUTON_CONNEXION = (By.CSS_SELECTOR, "button[type='submit']")
    MESSAGE_ERREUR = (By.CSS_SELECTOR, ".alert-danger")

    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)

    def ouvrir(self):
        self.driver.get(self.URL)
        return self

    def se_connecter(self, email, mot_de_passe):
        self.wait.until(
            EC.visibility_of_element_located(self.CHAMP_EMAIL)
        ).send_keys(email)
        self.driver.find_element(*self.CHAMP_MOT_DE_PASSE).send_keys(mot_de_passe)
        self.driver.find_element(*self.BOUTON_CONNEXION).click()

    def message_erreur(self):
        return self.wait.until(
            EC.visibility_of_element_located(self.MESSAGE_ERREUR)
        ).text


# Utilisation dans un test : le test ne connaît aucun sélecteur
driver = webdriver.Chrome()
page = PageConnexion(driver).ouvrir()
page.se_connecter("utilisateur@exemple.com", "mauvais_mot_de_passe")
assert "identifiants invalides" in page.message_erreur().lower()
driver.quit()

If the UI changes, you only modify the PageConnexion class: all the tests that use it remain intact.

Best Practices for Avoiding Flaky Tests

A “flaky” test fails randomly even though the code under test hasn’t changed. Here’s how to avoid that:

  • Ban time.sleep(): replace every sleep with an explicit wait on an observable condition.
  • Wait for the right condition: element_to_be_clickable before a click, invisibility_of_element_located on the loading spinner before reading a result.
  • Choose stable selectors: prefer test-dedicated attributes (data-testid) over automatically generated CSS classes.
  • Isolate your tests: each test creates its own data and doesn’t depend on the execution order of the others.
  • Fix the window size: a different responsive layout between your machine and the CI server changes how elements are arranged.
  • Capture the screen on failure: an image and the page’s HTML (driver.page_source) speed up diagnosis enormously.
  • Use driver.quit() in a finally block or a context manager so you never leave orphaned processes behind.

Conclusion

Selenium 4 with Python provides everything you need to automate a browser robustly: automatic driver management simplifies installation, the By class and CSS selectors cover element location, and explicit waits with WebDriverWait ensure reliability when dealing with asynchronous pages. For projects that go beyond a few scripts, the Page Object pattern structures your code and protects it from UI changes.

To go further, check out the official Selenium documentation and explore integration with pytest to organize your test suites.

InSkillCoach

About InSkillCoach

Training and technology expert

Coach specializing in advanced technologies and AI, backed by GNeurone Inc.

Certifications:

  • AWS Certified Solutions Architect – Professional
  • Google Cloud certifications
  • Microsoft Certified: DevOps Engineer Expert
  • Certified Kubernetes Administrator (CKA)
  • CompTIA Security+
1.3k
125

Comments

Comments are powered by GitHub Discussions

Sign in with GitHub to join the discussion

Link copied!