Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Código Simple en Java QA: Enseña Más, Confunde Menos
¿Alguna vez escribiste código en Java, lo mostraste a tu equipo y alguien preguntó
"¿y eso qué hace?" señalando una sola línea que para ti era obvia?
Eso pasa más de lo que creemos en automatización QA. Llevamos las herramientas al máximo,
usamos lambdas, streams y method chaining porque el IDE no protesta… y terminamos con
código que funciona perfecto pero que nadie más puede mantener.
En el stack SDTE trabajamos con equipos mixtos: hay Juniors que llevan 6 meses con Java
y Semi-seniors que ya tienen criterio propio. Si el código que generas o enseñas solo lo
entiende el nivel más alto, estás fallando al nivel más bajo — y eso cuesta caro en
onboarding, revisiones de código y bugs nocturnos.
La regla que adoptamos es simple: simple por defecto, avanzado solo si se pide.
Al final de este artículo encontrarás el Gancho Práctico con los ejemplos comparativos
listos para copiar en Selenium, Appium y Karate DSL.
No hay nada malo con las lambdas o los streams. El problema es usarlos como default cuando tu objetivo es enseñar, no impresionar.
Cuando un Junior ve esto por primera vez:
java
List<WebElement> visibles = driver.findElements(By.tagName("a"))
.stream()
.filter(WebElement::isDisplayed)
.collect(Collectors.toList());
Necesita entender simultáneamente: streams, method references, generics y la API de Selenium. Son cuatro conceptos a la vez. Si falla, no sabe en cuál de los cuatro está el error.
La versión simple separa esos conceptos en pasos que se pueden leer de arriba hacia abajo, depurar línea por línea y explicar en clase sin saltarse nada.
La simplicidad no es lo opuesto de la calidad. Es una decisión pedagógica consciente.
En el stack SDTE definimos dos niveles de escritura de código:
| Nivel | Cuándo usar | Características |
|---|---|---|
| NIVEL 1 — DEFAULT | Siempre, salvo que se pida otra cosa | for clásico, if/else explícito, variables intermedias con nombres descriptivos |
| NIVEL 2 — AVANZADO | Solo si el usuario pide: “versión avanzada”, “refactoriza”, “usa streams” | Streams, lambdas, method chaining, patrones funcionales |
Cinco reglas concretas del Nivel 1:
for clásico antes que stream() / forEach con lambdaif/else explícito antes que operador ternario encadenadoEl ejemplo más común en proyectos reales: filtrar una lista de elementos visibles.
java
// Capa: web-selenium | Patrón: sin separación de pasos
// Principio SOLID: funciona, pero sacrifica legibilidad
List<WebElement> visibles = driver.findElements(By.tagName("a"))
.stream()
.filter(WebElement::isDisplayed)
.collect(Collectors.toList());
java
// ============================================
// Filtrar elementos visibles — Selenium 4
// Capa : web-selenium
// Patrón : Page Object (este código va dentro de un método de página)
// Principio SOLID: S — cada método hace una sola cosa
// Nivel : Junior / Mixto
// ============================================
// Paso 1: obtener TODOS los enlaces de la página
List<WebElement> todosLosEnlaces = driver.findElements(By.tagName("a"));
// Paso 2: preparar la lista donde guardaremos solo los visibles
List<WebElement> enlacesVisibles = new ArrayList<>();
// Paso 3: recorrer cada enlace y filtrar los que están visibles en pantalla
for (WebElement enlace : todosLosEnlaces) {
if (enlace.isDisplayed()) { // solo agregar si el usuario lo puede ver
enlacesVisibles.add(enlace);
}
}
// Resultado: enlacesVisibles contiene solo los que están en pantalla
System.out.println("Enlaces visibles encontrados: " + enlacesVisibles.size());
Y el mismo principio aplica para interactuar con un elemento con espera explícita:
java
wait.until(ExpectedConditions.elementToBeClickable(By.id("btn-login"))).click();
java
// ============================================
// Clic con espera explícita — Selenium 4
// Capa : web-selenium
// Principio SOLID: S — cada variable tiene un propósito claro
// ============================================
// Paso 1: definir el locator del botón (separado para reusar si hace falta)
By localizadorBoton = By.id("btn-login");
// Paso 2: esperar hasta que el botón sea clickeable (máximo 10 segundos)
WebElement botonLogin = wait.until(
ExpectedConditions.elementToBeClickable(localizadorBoton)
);
// Paso 3: hacer clic ahora que sabemos que el botón está listo
botonLogin.click();
💡 Versión avanzada disponible: si quieres ver esta misma lógica con method chaining en una sola línea, pídemela.
En Mobile los mismos principios aplican. Dos casos frecuentes:
java
driver.findElements(By.xpath("//android.widget.TextView"))
.stream()
.filter(e -> e.getText().contains("Iniciar sesión"))
.findFirst()
.ifPresent(WebElement::click);
java
// ============================================
// Buscar y hacer clic en texto de lista — Appium 2
// Capa : mobile-appium
// Patrón : Screen Object (este método va en una Screen class)
// Principio SOLID: S — método busca Y hace clic en un solo elemento
// ============================================
// Paso 1: obtener todos los textos visibles en la pantalla actual
List<WebElement> textos = driver.findElements(
By.xpath("//android.widget.TextView")
);
// Paso 2: recorrer hasta encontrar el que necesitamos
for (WebElement texto : textos) {
if (texto.getText().contains("Iniciar sesión")) {
texto.click(); // encontrado: hacer clic
break; // salir del bucle, ya no necesitamos seguir buscando
}
}
java
String locator = plataforma.equals("Android") ? "//android.widget.Button"
: plataforma.equals("iOS") ? "//XCUIElementTypeButton"
: "//button";
java
// ============================================
// Seleccionar locator según plataforma — Appium 2
// Capa : mobile-appium
// Patrón : Strategy (locator cambia según la plataforma activa)
// Principio SOLID: O — agregar nueva plataforma = agregar else if, sin tocar lógica existente
// ============================================
String locator; // declarar primero, asignar según plataforma
if (plataforma.equals("Android")) {
locator = "//android.widget.Button"; // locator nativo Android
} else if (plataforma.equals("iOS")) {
locator = "//XCUIElementTypeButton"; // locator nativo iOS
} else {
locator = "//button"; // fallback web/híbrido
}
// Usar el locator ya seleccionado
WebElement boton = driver.findElement(By.xpath(locator));
boton.click();
💡 Versión avanzada disponible: si quieres ver esto con un
Map<String, String>o con el patrón Strategy completo, pídemela.
Karate tiene su propio lenguaje, pero el principio es el mismo: una acción por línea, variables con nombre descriptivo.
gherkin
* def token = karate.call('classpath:auth.feature')['access_token']
* configure headers = { Authorization: 'Bearer ' + token, 'Content-Type': 'application/json' }
gherkin
# ============================================
# Configurar headers de autenticación — Karate DSL
# Capa : api-karate
# Patrón : Template Method (Background ejecuta esto antes de cada Scenario)
# SOLID : S — Background solo configura, no valida negocio
# ============================================
# Paso 1: llamar al feature de login y guardar el resultado completo
* def resultadoAuth = karate.call('classpath:features/auth/login.feature')
# Paso 2: extraer solo el token del resultado (hace el código más legible)
* def accessToken = resultadoAuth['access_token']
# Paso 3: construir el valor del header Authorization
* def headerAuth = 'Bearer ' + accessToken
# Paso 4: configurar los headers para todas las llamadas siguientes
* configure headers = { Authorization: headerAuth, 'Content-Type': 'application/json' }
gherkin
* match response.data.users[?(@.activo == true)].length() == 3
gherkin
# Paso 1: extraer la lista de usuarios activos en una variable con nombre claro
* def usuariosActivos = response.data.users[?(@.activo == true)]
# Paso 2: validar la cantidad — ahora la intención es obvia al leer
* match usuariosActivos.length() == 3
💡 Versión avanzada disponible: si quieres ver la validación en una sola expresión JsonPath compacta, pídemela.
Aquí tienes la plantilla completa para aplicar esta regla en cualquier componente del stack SDTE. Cópiala y úsala como punto de partida:
java
// ============================================
// [NOMBRE DE TU COMPONENTE]
// Capa : [web-selenium | mobile-appium | framework-testng]
// Patrón : [nombre del patrón]
// Principio SOLID: [letra] — [nombre]
// Nivel : Mixto (Junior → Semi-senior)
// ============================================
// --- NIVEL 1 DEFAULT (siempre entregar esto primero) ---
// Paso 1: [acción clara con variable de nombre descriptivo]
TipoDato nombreDescriptivo = operacion();
// Paso 2: [siguiente acción — una sola cosa por paso]
if (condicion) {
// rama verdadera — explicar por qué
hacerAlgo(nombreDescriptivo);
} else {
// rama alternativa — explicar por qué
hacerOtraCosa();
}
// Paso 3: [resultado o verificación]
System.out.println("Resultado: " + nombreDescriptivo);
// --- NOTA AL FINAL (incluir siempre que exista versión avanzada) ---
// 💡 Versión avanzada disponible: si quieres ver esta misma lógica
// con [streams / lambdas / method chaining], pídemela.
Nivel 1 — Ejecuta (5 min) Toma uno de los snippets de Selenium de este artículo, agrégalo a un Page Object existente en tu proyecto y ejecútalo. Verifica que el output en consola muestra el número correcto de elementos visibles.
Objetivo: confirmar que el entorno funciona y entender QUÉ hace cada paso
Nivel 2 — Modifica (15 min) Cambia el locator By.tagName("a") por By.cssSelector(".nav-item") y adapta el comentario de cada paso para reflejar el nuevo contexto. Agrega un System.out.println dentro del for que imprima el texto de cada elemento visible.
Objetivo: entender CÓMO funciona el bucle y la condición de filtro
Nivel 3 — Extiende (30+ min) Toma el ejemplo de Karate DSL y agrega un Scenario Outline con una tabla de 3 usuarios distintos, validando que cada uno recibe su token correctamente. Usa los pasos separados (Nivel 1) para que cualquier miembro del equipo pueda leer el feature sin conocer Karate.
Objetivo: construir un flujo BDD + autenticación legible para QA funcional y técnico
¿Llegaste al Nivel 3? Comparte tu feature o tu Page Object en los comentarios 👇
Escribir código simple no es escribir código malo. Es una decisión de diseño que pone a las personas antes que a la sintaxis.
Tres puntos clave para llevarte hoy:
for, if/else y variables con nombre descriptivo deben ser tu primera opción en un equipo mixto¿Qué parte de tu stack SDTE implementarías con esta regla primero: Selenium, Appium o Karate?