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.
InSkillCoach
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
| Estrategia | Ejemplo | Cuándo usarla |
|---|---|---|
By.ID | By.ID, "email" | Siempre en primer lugar si existe un id estable |
By.NAME | By.NAME, "username" | Formularios clásicos |
By.CSS_SELECTOR | By.CSS_SELECTOR, "div.card > a" | Versátil, rápido y legible |
By.XPATH | By.XPATH, "//button[text()='OK']" | Búsqueda por texto o ascenso en el DOM |
By.CLASS_NAME | By.CLASS_NAME, "btn-primary" | Una sola clase, sin espacios |
By.TAG_NAME | By.TAG_NAME, "table" | Colecciones de elementos |
By.LINK_TEXT | By.LINK_TEXT, "Contact" | Enlaces cuyo texto exacto se conoce |
By.PARTIAL_LINK_TEXT | By.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ón | Comprueba que |
|---|---|
presence_of_element_located | El elemento existe en el DOM |
visibility_of_element_located | El elemento existe y es visible |
element_to_be_clickable | El elemento es visible y está habilitado |
text_to_be_present_in_element | Se muestra un texto determinado |
invisibility_of_element_located | Un elemento (spinner) ha desaparecido |
title_contains | El 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 cadasleeppor una espera explícita sobre una condición observable. - Espera la condición adecuada:
element_to_be_clickableantes de un clic,invisibility_of_element_locatedsobre 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 bloquefinallyo 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.
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+
Comentarios
Los comentarios funcionan con GitHub Discussions
Inicia sesión con GitHub para participar en la discusión