Sesión 5 de mi lab de Selenium + Java: Localizadores en Selenium

DOM, selector, locator y By: qué es cada cosa y cómo elegir el mejor. Jerarquía práctica de id a XPath. Primer test de login real en demo.serenity.is

Captura de pantalla mostrando Chrome DevTools inspeccionando el formulario de login de Serenity Demo, con el panel de elementos HTML visible
DevTools inspeccionando el login de Serenity: así es como Selenium "ve" los elementos

Inicio

"Abrir un sitio es fácil. Interactuar con él de forma mantenible es otra cosa."

En las sesiones anteriores armé el wiring básico del proyecto:
configuración, driver manager, primer test que abre el sitio.

En esta sesión doy un paso más importante:
interactuar con la interfaz de forma consciente para testear una funcionalidad real, en este caso el login.

El objetivo es que el test “funcione” y además conversar acerca de:

  • Localizar los elementos del sitio web

El primer flujo real: login

Realicé cambios en PrimerTest.java

Agregué líneas para poder loguearse en demo.serenity.is. Para esto tuve que usar localizadores de los elementos web del login:

  • input de usuario
  • input de password
  • y botón de iniciar sesión.

En este test probé el uso de esperas. Practiqué cómo se usan en el código y cuál es su resultado en la prueba.

IntelliJ IDEA mostrando el código de PrimerTest.java con los localizadores By.id para usuario, password y botón de login
Localizadores definidos con By.id() — el código completo del test de login

Código completo:

package com.cesar.qa.tests;

import com.cesar.qa.config.ConfigReader; //    - importar ConfigReader
import com.cesar.qa.config.DriverManager; //    - importar DriverManager

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.testng.annotations.Test;

import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
import java.time.Duration;

public class PrimerTest {

    @Test
    public void abrirSerenityis() {

        // 1) Inicializa el WebDriver según lo que diga config.properties
        //    - browser=chrome
        //    - headless=true/false
        //    - window.maximize=true/false
        //    - timeouts
        //    Nota: Esto evita tener "new ChromeDriver()" en cada test.
        DriverManager.initDriver();

        // 2) Obtiene la instancia del driver creada por DriverManager
        //    (Si todavía no existe, la crea automáticamente).
        WebDriver driver = DriverManager.getDriver();

        // 3) Lee la URL base desde config.properties
        //    Ej: base.url=https://demo.serenity.is
        String baseUrl = ConfigReader.getProperty("base.url");

        // 4) Navega a la URL configurada
        //    (Esto reemplaza el hardcode: driver.get("https://demo.serenity.is/..."))
        driver.get(baseUrl);

        // 3) Localiza inputs
        By usuarioInput = By.id("LoginPanel0_Username");
        By passwordInput = By.id("LoginPanel0_Password");
        By loginButton = By.id ("LoginPanel0_LoginButton");

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

        // 4) Limpia e ingresa usuario
        WebElement usuario = wait.until(ExpectedConditions.visibilityOfElementLocated(usuarioInput));
        usuario.clear();
        usuario.sendKeys("admin");

        // 5) Limpia e ingresa password
        WebElement password = wait.until(ExpectedConditions.visibilityOfElementLocated(passwordInput));
        password.clear();
        password.sendKeys("serenity");

        // 6) Click en iniciar sesión
        wait.until(ExpectedConditions.elementToBeClickable(loginButton)).click();

        // (opcional) pequeña validación visual / pausa
        System.out.println("Login ejecutado");

        // 5) Validación / evidencia simple: imprimir el título de la página
        //    En esta etapa, el objetivo es confirmar que todo el wiring funciona.
        String titulo = driver.getTitle();
        System.out.println("Título de la página: " + titulo);

        // 6) Cierra el navegador y limpia el driver para la próxima ejecución
        //    (Más adelante esto se moverá a un BaseTest con @AfterMethod)
        // DriverManager.quitDriver();

        System.out.println("Test ejecutado correctamente");
    }



}

Resultado de la ejecución

IntelliJ IDEA mostrando la consola de ejecución con el resultado del test: Tests passed 1, Failures 0, exit code 0
Test ejecutado: 1 passed, 0 failures, exit code 0

todo ok 👍

El test abre login y limpia las credenciales que vienen por defecto en los campos de usuario y password y luego hace click en iniciar sesión.

Navegador Chrome controlado por Selenium mostrando el formulario de login de Serenity Demo con credenciales ingresadas
Chrome automatizado: el test ingresó las credenciales y está listo para hacer click en "Iniciar sesión"

y abre el dashboard

Dashboard de Serenity Demo después del login exitoso, mostrando métricas de Open Orders, Closed Orders, Total Customers y Product Types
Login exitoso — el test llegó al dashboard de Serenity

Este es el primer flujo que automatizo en Serenity Demo.

A continuación hablo acerca de los localizadores.

Charlo acerca de lo que es más usado y porqué, de acuerdo a lo que investigué.


DOM, selector, locator y By: qué es cada cosa (sin confusión)

Cuando empezás con Selenium aparecen palabras que parecen sinónimos, pero no lo son.
Si no las entendés bien, terminas escribiendo tests “a prueba y error”.

Vamos por partes.


DOM: lo que Selenium realmente ve

DOM (Document Object Model) es la representación en memoria que el navegador construye de una página web.
Diagrama del DOM Tree mostrando la estructura jerárquica: Document, HTML, Head, Body, y sus nodos hijos como div, p, h1, ul, li
El DOM es un árbol de objetos, no texto HTML — así lo recorre Selenium

No es el HTML en texto.
Es un árbol de objetos que el navegador construye para poder:

  • mostrar la página
  • aplicar CSS
  • ejecutar JavaScript
  • y permitir que herramientas como Selenium encuentren elementos

HTML vs DOM (clave)

HTML (archivo)

<input id="username" type="text">

Es solo texto.

DOM (en memoria)

Document
 └── html
     └── body
         └── input
              ├── id = "username"
              └── type = "text"

👉 El navegador parsea el HTML y lo convierte en este árbol.


Por qué se dice “árbol DOM”

Porque:

  • cada elemento es un nodo
  • tiene padres, hijos y hermanos
  • hay una raíz (document)

Ejemplo:

<form>
  <input />
  <button>Login</button>
</form>

Relaciones:

  • form → padre
  • input y button → hijos
  • inputbutton → hermanos

Selenium NO busca en el HTML

Esto es muy importante:

Selenium no lee el archivo HTML
Selenium interactúa con el DOM vivo

Por eso:

  • elementos que aparecen con JS → Selenium sí los ve
  • elementos ocultos → existen en el DOM
  • cambios dinámicos → el DOM cambia

Selectores y DOM

Un selector siempre apunta al DOM.

Ejemplos:

➡ busca en el DOM un nodo con id="username"

By.id("username")

➡ navega el árbol DOM:
form → hijo input

By.cssSelector("form input")

➡ busca un nodo button cuyo texto sea “Login”

By.xpath("//button[text()='Login']")

DOM ≠ lo que ves en pantalla

Hay cosas que:

  • existen en el DOM
  • pero no son visibles

Ejemplos:

  • display: none
  • visibility: hidden
  • elementos fuera del viewport
  • modales ocultos

👉 Selenium puede encontrarlos, pero no siempre interactuar con ellos.


Definición para QA (la buena)

El DOM es la estructura viva que Selenium inspecciona, recorre y manipula para automatizar una web.

Si entendés el DOM:

  • elegís mejores locators
  • evitás XPath frágiles
  • entendés por qué fallan los waits
  • dejás de “probar a ciegas”

Selector: cómo identificás un elemento en el DOM

Es la forma concreta de identificar un elemento en el DOM.

Ejemplos:

  • #login-button
  • input[type='email']
  • //button[text()='Login']
  • username

👉 Esto existe antes de Selenium (HTML / CSS / XPath).

Los selectores no son de Selenium.
Vienen de:

  • CSS
  • XPath
  • HTML

Orden de prioridad para elegir selectores

1️⃣ id (si existe y es estable)

By.id("login-button")

✔ Único
✔ Rápido
✔ Legible
✔ Muy estable

Regla:

Si hay id y no cambia → usalo sin pensar.

2️⃣ data-* (ideal en apps modernas)

By.cssSelector("[data-testid='login-btn']")

✔ Diseñados para testing
✔ No dependen del layout
✔ Ultra estables

👉 Si podés pedirlos al dev, pedilos.

3️⃣ CSS selector por atributos

By.cssSelector("input[type='email']")
By.cssSelector("button[name='submit']")

✔ Potente
✔ Más corto que XPath
✔ Bastante estable

4️⃣ CSS por jerarquía simple

By.cssSelector("form#loginForm button[type='submit']")

✔ Legible
⚠ Frágil si cambia la estructura

Usar solo si no hay algo mejor.

5️⃣ XPath simple (cuando CSS no alcanza)

By.xpath("//button[text()='Login']")

✔ Accede al texto
✔ Útil para validaciones

⚠ Frágil si cambia idioma o copy

6️⃣ XPath complejo / absoluto (último recurso)

By.xpath("/html/body/div[2]/form/div[3]/button")

❌ Muy frágil
❌ Difícil de leer
❌ Se rompe con cualquier cambio

👉 Evitar siempre que sea posible.


Selectores a evitar (salvo emergencia)

  • By.className con clases de framework (css-123abc)
  • XPath con índices (div[3]/span[2])
  • Selectores basados en estilos (color, position)
  • Textos cambiantes (“Aceptar”, “Confirmar”, etc.)

Regla de oro (la que realmente importa)

Cuanto más cerca estés del negocio
y más lejos del layout,
mejor es el selector.

Jerarquía resumida

1. id
2. data-* (data-testid, data-qa, etc.)
3. CSS por atributo
4. CSS por jerarquía simple
5. XPath simple
6. XPath complejo (evitar)

Elegir un selector no es una decisión técnica menor.
Es una decisión de mantenibilidad.

Un buen selector apunta al significado del elemento,
no a cómo está dibujado en pantalla.

Si mañana cambia el layout y tu test se rompe,
el selector era el problema.

Locator: el selector entendido por Selenium

Un locator es el selector envuelto en algo que Selenium entiende.

Es la abstracción que usa la herramienta de automatización para localizar un elemento, usando un selector.

En Selenium Java, el locator es:

By.id("login-button")

👉 El locator incluye:

  • el tipo de selector (id, css, xpath…)
  • el valor del selector

En Selenium se usa además un “envoltorio”, la clase By.

Ejemplo:

By loginButton = By.id("login-button");

Acá:

  • "login-button" → es el selector
  • By.id(...) → es la estrategia
  • todo junto → es el locator
El locator es lo que Selenium usa para localizar un elemento en el DOM.

Relación exacta entre Locator y Selector

Selector = “cómo identificar”
Locator = “cómo Selenium lo representa”

En Selenium:

By.cssSelector("input[type='email']")
  • cssSelector → tipo de selector
  • "input[type='email']" → selector
  • todo junto → locator

Ejemplo comparativo

Selector (conceptual / DOM)

#submit-btn

Locator (Selenium Java)

By submitButton = By.cssSelector("#submit-btn");

En el día a día como QA

En conversaciones reales vas a escuchar:

“Ese locator es frágil”
“Cambió el selector”
“Arreglé los locators del login”

Y está bien. Nadie te va a corregir eso.


Regla práctica (la que importa)

ContextoPalabra correcta
HTML / CSS / XPathSelector
Selenium / Playwright / CypressLocator
Charla informalSon sinónimos
Diseño de frameworkLocator

Frase para quedarte

El selector identifica.
El locator encapsula esa identificación para la herramienta.

By: la sintaxis de Selenium (no el selector)

By es parte de la sintaxis de Selenium para expresar selectores, no es el selector en sí.

Dicho simple:

El selector es el criterio (id, css, xpath, name, etc.)
By es el wrapper que Selenium usa para entenderlo

Qué es realmente By en Selenium (Java)

By es una clase de Selenium que representa una estrategia de búsqueda de elementos.

Cuando escribís:

By.id("username")

lo que estás diciendo es:

“Buscá un elemento por ID cuyo valor sea username”.


Sintaxis general

Todos los selectores en Selenium Java siguen esta forma:

By.<tipoDeSelector>(valor)

Ejemplos:

By.id("email")
By.name("password")
By.className("btn-primary")
By.cssSelector("button[type='submit']")
By.xpath("//input[@type='text']")

👉 By es obligatorio en Selenium Java para localizar elementos.


Importante: By no es Selenium “sucio”

Mucha gente piensa:

“Quiero evitar By

Pero el objetivo no es evitarlo, sino aislarlo.

Ejemplo malo (ruido en el test):

driver.findElement(By.id("username")).sendKeys("admin");
driver.findElement(By.id("password")).sendKeys("1234");
driver.findElement(By.id("login")).click();

Ejemplo bueno (By aislado):

// Locators
private By usernameInput = By.id("username");
private By passwordInput = By.id("password");
private By loginButton = By.id("login");
// Uso
type(usernameInput, "admin");
type(passwordInput, "1234");
click(loginButton);
👉 El test lee negocio, no sintaxis.

Resumen mental

  • By no es el selector
  • By es la forma que Selenium usa para interpretar el selector
  • ✅ El selector real es: id, css, xpath, etc.
  • 🎯 Buen diseño = selectores aislados, tests limpios

Cómo se conectan todos los conceptos

La cadena completa es esta:

DOM → selector → locator (By) → Selenium

Ejemplo mental:

“En el DOM hay un botón.
Lo identifico con un selector CSS.
Selenium lo envuelve en un By.
Y recién ahí puede interactuar.”

Por qué esto importa al diseñar un framework

El objetivo no es evitar By, sino aislarlo.

Mal diseño (ruido en el test):

driver.findElement(By.id("login")).click();

Buen diseño (intención clara):

private By loginButton = By.id("login");
click(loginButton);

Los locators no son parte del negocio, son parte de la infraestructura del test.
Por eso necesitan vivir en un lugar donde puedan cambiar sin romper la lógica de las pruebas.
Estos necesitaran cambios de acuerdo a nuevas versiones de la web, que llegan con cambios en la UI.

Por otro lado, en otra parte, hay que tener los tests, las pruebas que necesita el negocio.
Separadas, aisladas y que no contengan código que distraiga o de problemas.
Lo fundamental es el negocio, hacer las pruebas que importan.

👉 El test habla de negocio.
👉 La sintaxis queda escondida.

Para cerrar, volver a indicar lo importante a considerar en la elección del selector y locator:

Cuanto más cerca estés del negocio
y más lejos del layout,
mejor es el selector.

En la siguiente sesión hablare acerca de:

  • El tiempo para encontrar los elementos de la web y simular escribir, hacer click en ellos, o sea el tiempo en las pruebas
  • Y el porqué se busca estructurar el código, aislar código que se repite mucho y dejar código de pruebas de negocio lo más limpio posible

🔗 Todo el código de esta serie está en: github.com/cesarbeassuarez/qa-automation-lab
📂 selenium-java

Temas conectados:

Sesión 3 de este lab y Sesión 4 de este lab de Selenium con java.