0%
Selenium con Python: Automatizar el Navegador

Selenium con Python: Automatizar el Navegador

Aprende a automatizar el navegador con Selenium 4 y Python: instalación, localización de elementos, esperas explícitas, modo headless y Page Object.

I

InSkillCoach

· min

Selenium con Python: Automatizar el Navegador

Selenium es la biblioteca de referencia para controlar un navegador web desde un programa. Tanto si quieres automatizar pruebas de interfaz, rellenar formularios repetitivos o extraer datos de páginas generadas con JavaScript, Selenium te ofrece un control completo sobre Chrome, Firefox o Edge. Esta guía cubre lo esencial de Selenium 4 con Python: desde la instalación hasta el patrón Page Object, pasando por las esperas explícitas, que marcan la diferencia entre un script frágil y uno fiable.

Instalación

Desde Selenium 4.6, la gestión de los drivers es automática gracias a Selenium Manager, integrado en la biblioteca. Ya no es necesario descargar manualmente chromedriver o geckodriver: Selenium detecta tu navegador y obtiene el driver correspondiente.

# Installation de Selenium 4
pip install selenium

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

Basta con asegurarse de tener un navegador instalado en la máquina (Chrome, Firefox o Edge).

Primer script

Este es un script mínimo que abre una página, lee su título y cierra el navegador:

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()

Existen dos métodos de cierre: driver.close() cierra la pestaña actual, mientras que driver.quit() cierra todas las ventanas y finaliza el proceso del driver. Prefiere siempre quit() al final del script.

Localizar elementos

Toda interacción empieza por la localización de un elemento en el DOM. Selenium 4 utiliza la clase By combinada con find_element (primer elemento encontrado) o find_elements (lista con todos los elementos).

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()

Tabla de estrategias de localización

EstrategiaEjemploCuándo usarla
By.IDBy.ID, "email"Siempre en primer lugar si existe un id estable
By.NAMEBy.NAME, "username"Formularios clásicos
By.CSS_SELECTORBy.CSS_SELECTOR, "div.card > a"Versátil, rápido y legible
By.XPATHBy.XPATH, "//button[text()='OK']"Búsqueda por texto o ascenso en el DOM
By.CLASS_NAMEBy.CLASS_NAME, "btn-primary"Una sola clase, sin espacios
By.TAG_NAMEBy.TAG_NAME, "table"Colecciones de elementos
By.LINK_TEXTBy.LINK_TEXT, "Contact"Enlaces cuyo texto exacto se conoce
By.PARTIAL_LINK_TEXTBy.PARTIAL_LINK_TEXT, "Cont"Enlaces cuyo texto varía ligeramente

Como regla general: da prioridad a By.ID y después a los selectores CSS. Reserva XPath para los casos que CSS no cubre (selección por texto, navegación hacia un elemento padre).

Interactuar con los elementos

Clic e introducción de texto

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()

Listas desplegables

Para los elementos <select>, Selenium proporciona la clase de utilidad Select:

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)

Leer las propiedades de un elemento

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

Esperas: implícitas vs. explícitas

Este es el punto más importante de la guía. Las páginas modernas cargan su contenido con JavaScript de forma asíncrona: puede que un elemento aún no exista en el momento en que tu script lo busca. Sin espera, obtendrás errores NoSuchElementException aleatorios.

Espera implícita

La espera implícita se aplica de forma global: en cada find_element, Selenium reintenta durante el tiempo indicado antes de lanzar una excepción.

driver = webdriver.Chrome()

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

Es sencilla, pero insuficiente: la espera implícita solo comprueba la presencia en el DOM, no la visibilidad ni que se pueda hacer clic. Un elemento puede existir pero estar oculto o deshabilitado.

Espera explícita (recomendada)

La espera explícita apunta a una condición concreta sobre un elemento concreto, con WebDriverWait y el módulo expected_conditions:

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()

Las condiciones más útiles:

CondiciónComprueba que
presence_of_element_locatedEl elemento existe en el DOM
visibility_of_element_locatedEl elemento existe y es visible
element_to_be_clickableEl elemento es visible y está habilitado
text_to_be_present_in_elementSe muestra un texto determinado
invisibility_of_element_locatedUn elemento (spinner) ha desaparecido
title_containsEl título de la página contiene una cadena

Regla de oro: no uses nunca time.sleep() para esperar a un elemento. Un sleep demasiado corto vuelve el script inestable y uno demasiado largo lo ralentiza sin necesidad. La espera explícita termina en cuanto se cumple la condición. Evita también mezclar esperas implícitas y explícitas: los tiempos de espera pueden acumularse de forma impredecible.

Modo headless

El modo headless ejecuta el navegador sin interfaz gráfica. Es indispensable en un servidor de integración continua:

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()

Fija siempre el tamaño de la ventana en headless: por defecto es pequeña y algunos elementos pueden quedar ocultos por un diseño responsive.

Capturas de pantalla

Las capturas son valiosas para diagnosticar los fallos de las pruebas:

# 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")

Buena práctica: captura sistemáticamente una imagen en el bloque except de tus pruebas para ver el estado de la página en el momento del fallo.

Iframes y ventanas múltiples

Iframes

Un elemento situado dentro de un <iframe> es invisible para Selenium mientras no cambies el contexto:

# 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()

Ventanas y pestañas

Cada ventana tiene un identificador (handle). Para seguir un enlace que abre una nueva pestaña:

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)

El patrón Page Object

Cuando los scripts crecen, los selectores se repiten por todas partes y cada cambio de interfaz rompe decenas de líneas. El patrón Page Object resuelve este problema: cada página del sitio se representa mediante una clase que encapsula sus selectores y sus acciones.

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()

Si la interfaz cambia, solo modificas la clase PageConnexion: todas las pruebas que la utilizan permanecen intactas.

Buenas prácticas anti-flaky

Una prueba «flaky» falla de forma aleatoria sin que el código probado haya cambiado. Así se evita:

  • Destierra time.sleep(): sustituye cada sleep por una espera explícita sobre una condición observable.
  • Espera la condición adecuada: element_to_be_clickable antes de un clic, invisibility_of_element_located sobre el spinner de carga antes de leer un resultado.
  • Elige selectores estables: prefiere los atributos dedicados a las pruebas (data-testid) frente a las clases CSS generadas automáticamente.
  • Aísla las pruebas: cada prueba crea sus propios datos y no depende del orden de ejecución de las demás.
  • Fija el tamaño de la ventana: un diseño responsive distinto entre tu máquina y el servidor de CI cambia la disposición de los elementos.
  • Captura la pantalla en caso de fallo: una imagen y el HTML de la página (driver.page_source) aceleran enormemente el diagnóstico.
  • Usa driver.quit() en un bloque finally o en un gestor de contexto para no dejar nunca procesos huérfanos.

Conclusión

Selenium 4 con Python ofrece todo lo necesario para automatizar un navegador de forma robusta: la gestión automática de los drivers simplifica la instalación, la clase By y los selectores CSS cubren la localización, y las esperas explícitas con WebDriverWait garantizan la fiabilidad frente a páginas asíncronas. En los proyectos que superan unos pocos scripts, el patrón Page Object estructura el código y lo protege de los cambios de la interfaz.

Para ir más allá, consulta la documentación oficial de Selenium y explora la integración con pytest para organizar tus suites de pruebas.

InSkillCoach

Acerca de InSkillCoach

Experto en formación y tecnologías

Coach especializado en tecnologías avanzadas e IA, impulsado por GNeurone Inc.

Certificaciones:

  • AWS Certified Solutions Architect – Professional
  • Certificaciones de Google Cloud
  • Microsoft Certified: DevOps Engineer Expert
  • Certified Kubernetes Administrator (CKA)
  • CompTIA Security+
724
261

Comentarios

Los comentarios funcionan con GitHub Discussions

Inicia sesión con GitHub para participar en la discusión

¡Enlace copiado!