Fundamentos de programación para QA Testers.
De tester manual a Quality Engineer, un concepto a la vez.
Bienvenida. Encuadre: hoy NO vamos a automatizar — hoy construimos las bases para que la próxima sesión escribamos scripts reales con Playwright. Menciona que hay quizzes con puntaje (esquina superior derecha) y zonas interactivas donde ellos participan. Pide que nadie se quede con dudas: el objetivo es entender CADA palabra del código.
| Test case manual | Script automatizado |
|---|---|
| Pasos numerados (1, 2, 3…) | Líneas de código (se ejecutan en orden) |
| Datos de prueba ("usuario: ana@qa.com") | Variables (const email = "ana@qa.com") |
| Resultado esperado | expect(...) — la aserción |
| Precondiciones | Setup / hooks |
| Lo ejecuta un humano 🧍 | Lo ejecuta la máquina 🤖 (miles de veces, sin café) |
Un script es un test case que la máquina ejecuta por ti. Hoy solo aprendemos el idioma para escribirlo.
Mensaje anti-miedo: ellos YA piensan como programadores cuando escriben un test case — pasos ordenados, datos, resultado esperado. Programar es traducir eso a un idioma que la máquina entiende. Pregunta a la audiencia: "¿qué pasa si el paso 3 de tu test case es ambiguo?" — igual en código, la máquina necesita instrucciones exactas.
Empezamos en un dato diminuto y alejamos la cámara slide a slide hasta ver un repositorio completo.
Valores, tipos, variables, operadores.
Funciones, decisiones, loops, datos.
Tipos que te protegen. Esperas elegantes.
Anatomía Playwright + playground en vivo.
El repo completo. Ejecutar y leer resultados.
8 quizzes. Tu puntaje vive arriba a la derecha. Los playgrounds se tocan.
Explica la mecánica del zoom: el mismo código va a crecer frente a ellos — primero un valor, luego una variable, luego una función… hasta un repo. Nada aparece de la nada. Anuncia los quizzes: uno por concepto, puntaje acumulado, sin castigo por equivocarse. Duración estimada: 90-120 min con pausas.
Esto es un valor: la pieza más pequeña de información que un programa puede manejar. Las comillas dicen "esto es texto". Sin contexto, sin nombre, sin lógica. Un átomo.
Slide minimalista a propósito: UNA idea. Un valor es un dato suelto. Compáralo con una celda suelta de su tabla de test data. Pregunta retórica: "¿de qué sirve un dato sin nombre?" — eso conecta con la siguiente slide (variables).
"Ana" "ana@qa.com" "Login exitoso"
Texto. Siempre entre comillas.
25 3.14 404
Números. Sin comillas. 404 ≠ "404"
true false
Verdadero o falso. El tipo favorito de QA: ¿pasó o falló?
Analogía QA: los campos de un formulario. Nombre = string, edad = number, "acepto términos" = boolean.
Tres tipos primitivos bastan para el 90% de los tests. Enfatiza la trampa de 404 vs "404": un status code numérico y un texto que parece número NO son lo mismo — esto vuelve en el quiz de operadores. Boolean es EL tipo de QA: toda aserción termina en true/false.
const nombre = "Ana"; const edad = 25; let intentos = 0;
const/let → "voy a crear una caja"
nombre → la etiqueta de la caja
= → "guarda esto adentro"
"Ana" → el valor guardado
Caja sellada. El valor no se puede reemplazar. Tu opción por defecto.
Caja reutilizable. El valor puede cambiar (contadores, reintentos).
const email = "ana@qa.com"
Tus datos de prueba con nombre propio.
El desglose palabra por palabra es la clave de esta slide — léelo despacio. Regla práctica: usa const siempre; let solo cuando NECESITES cambiar el valor (la herramienta te avisará). En el playground siguiente van a romper un const a propósito para ver el error.
Primer momento interactivo: pide a alguien de la audiencia que dicte un cambio. Ejecuta el reto 2 — reasignar un const lanza "Assignment to constant variable" y ES BUENO: el lenguaje los protege. console.log = "muéstrame esto en la consola"; es la linterna del programador. Dedica 3-4 minutos.
TypeError: Assignment to constant variable. let sí permite cambiar el valor.Primer quiz — establece el ritmo: leen, alguien vota en voz alta, click. Si aciertan a la primera suma punto. Refuerza: el error de const no es un bug tuyo, es el lenguaje protegiéndote de cambios accidentales.
5 + 3 // 8 10 - 4 // 6 "QA" + "!" // "QA!"
Con strings, + une texto.
5 === 5 // true 5 !== 3 // true 10 > 7 // true
Siempre devuelven boolean.
a && b // Y: ambas a || b // O: alguna !a // NO: invierte
Combinan condiciones.
La trampa #1: = asigna (guarda en la caja) · === compara (¿son iguales?). Confundirlos es el bug clásico de principiante.
Tres familias, pero el 80% del tiempo usarán === para comparar. Insiste en la trampa = vs ===: una asigna, la otra pregunta. Menciona que existe == (doble), pero la regla profesional es usar SIEMPRE === (triple), que también compara el tipo — perfecto puente al quiz.
console.log(5 === "5")?=== compara valor y tipo. 5 es number, "5" es string → false. En testing esto importa: un API que devuelve "404" (texto) no es lo mismo que 404 (número).Este quiz suele dividir a la sala. La explicación conecta con APIs: status codes como texto vs número es un bug real que verán en integración. Si alguien menciona ==, celebra la pregunta y reafirma: siempre ===.
function login(email, password) { const mensaje = "Bienvenida " + email; return mensaje; } const resultado = login("ana@qa.com", "secreto123");
"Voy a definir una receta."
Nombre + parámetros: los ingredientes que recibirá.
Lo que la receta entrega al terminar.
Llamarla = ejecutar la receta con ingredientes reales.
Distinción clave que confunde a todos: DEFINIR una función (escribir la receta) no la ejecuta; LLAMARLA (con paréntesis y argumentos) sí. Parámetros = nombres genéricos en la receta; argumentos = ingredientes concretos al llamar. El zoom creció: la variable de hace 5 slides ahora vive DENTRO de una función.
TC-101: abrir login, escribir email, escribir password, click…
TC-102: abrir login, escribir email, escribir password, click…
TC-103: abrir login, escribir email… otra vez. y otra. y otra.
login("ana@qa.com", "secreto123"); login("luis@qa.com", "otraClave9"); login("mia@qa.com", "claveMia55");
Escribes los pasos una vez, los ejecutas con mil datos distintos.
Una función es un test step reutilizable. Cuando el botón de login cambie, corriges UN lugar — no 40 test cases.
Este es el argumento de venta de la automatización entera: mantenimiento. Pregunta: "¿cuántos test cases tuyos repiten los mismos 4 pasos de login?" La respuesta emocional a esa pregunta ES la motivación para aprender funciones.
Deja que alguien resuelva el reto 2 en vivo (o guíalos línea a línea). Errores esperables: olvidar return (imprime undefined — explícalo: la función no entregó nada), olvidar paréntesis al llamar. Ambos errores son ORO pedagógico, no los esquives.
function sum(a, b) { return a + b; } console.log(sum(2, 3));
2 y 3 son numbers (sin comillas) → + suma: 5. Si fueran "2" y "3" (strings), + los pegaría: "23". El tipo decide el comportamiento.Combina funciones con la lección de tipos: el MISMO operador + se comporta distinto según el tipo. Si la sala duda entre A y B, vuelve un segundo a la slide de tipos. Este "aha" previene bugs reales de concatenación accidental.
if (statusCode === 200) { console.log("✅ Test PASSED"); } else { console.log("❌ Test FAILED"); }
statusCode === 200 se evalúa a true o false (¡los booleans del Acto 1!).
❌ false → bloque del else. Nunca ambos.
Es tu resultado esperado: "SI el sistema muestra X, el test pasa; SI NO, falla". Llevas años escribiendo if/else en español.
Conecta con el boolean del Acto 1: la condición del if ES una expresión que produce true/false. El paralelo con "resultado esperado" de sus test cases es literal — toda aserción de Playwright es un if/else glorificado que decide PASSED o FAILED.
forfor (let i = 0; i < 3; i++) { console.log("Intento #" + i); } // Intento #0, #1, #2
Tres piezas: inicio (i = 0) · condición (i < 3: ¿sigo?) · paso (i++: suma 1).
for...ofconst users = ["Ana", "Luis", "Mia"]; for (const user of users) { login(user); }
"Por cada user de la lista, haz login." Esto es data-driven testing: mismos pasos, N datos.
No profundices en la sintaxis del for clásico todavía — la siguiente slide lo ejecuta paso a paso visualmente. El mensaje: loops convierten "ejecutar el mismo test con 50 usuarios" de una pesadilla manual a 3 líneas. for...of es el que más usarán.
— vacía —
La slide más importante del Acto 2. Avanza paso a paso PREGUNTANDO antes de cada click: "¿qué línea sigue? ¿cuánto vale i?". Los loops dejan de ser magia cuando VEN a i mutar 0→1→2→3 y la condición volverse false. Tómate tu tiempo: 5 minutos aquí valen oro.
for (let i = 0; i < 3; i++) console.log(i)?i empieza en 0 (los programadores cuentan desde cero) y el loop corre mientras i < 3 — cuando i llega a 3, la condición es false y se detiene sin imprimir el 3.Acaban de VER esto en el visualizador, así que la tasa de acierto debería ser alta — refuerzo positivo. Si fallan, vuelve una slide y repite los pasos 2-3 del visualizador. "Contar desde cero" reaparece en arrays a continuación.
const browsers = ["chrome", "firefox", "safari"]; browsers[0] // "chrome" ← ¡desde cero! browsers[2] // "safari" browsers.length // 3 browsers.push("edge"); // agrega al final
Cada posición tiene número, empezando en 0. El primero es [0], no [1].
Cuántos elementos hay. Lo usaste en el loop: i < users.length.
Una columna de tu tabla de test data: lista de emails, de browsers, de roles a probar.
El índice desde cero ya lo vieron dos veces (loop, quiz) — tercera exposición, ya debería asentarse. push es el único método que muestro: suficiente para armar datos. La analogía columna-de-test-data prepara la siguiente slide donde objetos = fila completa.
const user = { email: "ana@qa.com", password: "secreto123", role: "admin", active: true, }; user.email // "ana@qa.com" user.role // "admin"
Pares nombre: valor. Como un formulario relleno: cada campo tiene etiqueta y contenido.
user.email = "del objeto user, dame email". Lo verás MIL veces: page.click es exactamente esto.
Array de objetos = tu tabla de test data completa: cada objeto una fila, cada propiedad una columna.
EL insight de esta slide: el punto de user.email es el MISMO punto de page.click() y expect().toBeVisible(). Cuando lleguen a Playwright ya habrán usado esa sintaxis 20 veces. Array de objetos = tabla de test data: cierra el círculo con su mundo conocido.
Este playground integra TODO el acto: array + objetos + for...of + if/else + comparación. Díselos explícitamente: "hace 30 minutos no podían leer esto; ahora pueden escribirlo". El reto introduce else if naturalmente. Es además un mini test RBAC real — admin vs viewer es exactamente lo que se prueba en apps multi-rol.
const fruits = ["uva", "kiwi", "mango"]; console.log(fruits[1]);
fruits[0] es "uva", fruits[1] es "kiwi". Error clásico de todo principiante (y de varios seniors a las 6pm).Cuarta exposición al índice-desde-cero. A esta altura deberían acertar casi todos — celebra el progreso del score. Cierra el Acto 2: "ya tienen TODA la lógica que un test necesita". Buen punto para pausa de 5 minutos si la sesión es de 2 horas.
function login(email: string, password: string) { // ... } login("ana@qa.com", 12345);
email: string declara qué tipo acepta cada parámetro. El contrato queda escrito.
El subrayado rojo aparece mientras escribes — no en producción, no en el test run de las 3am.
Encontrar el defecto lo más temprano posible… TypeScript es shift-left para tu propio código. 😉
Frame ganador: TypeScript no es "JS más difícil", es JS con un revisor automático. La conexión con shift-left les llega directo — detectan el bug en la fase más barata. El subrayado rojo de la slide es idéntico al que verán en VS Code. Bonus: los tipos alimentan el autocompletado, su mejor amigo al empezar.
const email: string = "ana@qa.com"; const retries: number = 3; const passed: boolean = true; interface User { email: string; role: "admin" | "viewer"; }
: tipo después del nombre. La mayoría de veces TypeScript lo deduce solo y ni lo escribes.
El "plano" de un objeto: qué campos y de qué tipo. Tu test data con contrato.
"admin" | "viewer"Solo estos valores exactos. Escribes "admni" → error al instante. Adiós typos silenciosos.
No memorizar sintaxis — reconocerla al leerla. La inferencia es importante para bajar ansiedad: casi nunca anotan tipos a mano al empezar. El union type es el ejemplo más QA-friendly: un typo en un rol o un status es un bug que TypeScript mata gratis.
La web es lenta: la página carga, el servidor responde, la animación termina… Tu código corre en milisegundos; la web responde en segundos.
// 🤖💥 robot impaciente (SIN await): page.goto("https://app.com/login"); page.click("#login-btn"); // click sobre una página que AÚN NO CARGÓ ❌
async function runTest() { await page.goto("https://app.com/login"); await page.click("#login-btn"); }
await = "espera a que esto termine antes de seguir".async = "esta función contiene esperas".
Regla de oro Playwright: casi toda acción lleva await. Olvidarlo = tests intermitentes (flaky).
NO expliques promesas ni event loop — agujero negro innecesario hoy. El modelo mental del robot impaciente basta: la máquina es más rápida que la web y hay que frenarla. Regla mecánica: acción de Playwright → await adelante; función con awaits → async adelante. El 90% de tests flaky de principiante = un await olvidado.
await page.click("#save-btn")?await, el código sigue corriendo sin esperar el click → el siguiente paso actúa sobre una página a medio cambiar → test flaky (a veces pasa, a veces no). El await olvidado es la causa #1 de tests intermitentes.La opción A es la trampa peligrosa: sin await a veces SÍ funciona (por suerte de timing) y por eso el bug es traicionero. Introduce la palabra "flaky" aquí — la van a escuchar toda su carrera. Fin del Acto 3: ya tienen todas las piezas del idioma.
import { test, expect } from "@playwright/test"; test("login exitoso", async ({ page }) => { await page.goto("https://app.com/login"); await page.fill("#email", "ana@qa.com"); await page.fill("#password", "secreto123"); await page.click("#login-btn"); await expect(page.locator("#welcome")).toBeVisible(); });
"Tráeme las herramientas test y expect de la caja Playwright."
¡Una función que recibe el nombre del caso… y otra función con los pasos! Sí: las funciones viajan como valores.
Funciones con parámetros (Acto 2) + el punto de objetos (Acto 2) + await (Acto 3).
Tu resultado esperado hecho código. Si no se cumple → test FAILED.
Slide cumbre: recorre el código línea por línea preguntando "¿qué reconocen aquí?" — strings, funciones, params, el punto, async/await: TODO visto. Único concepto nuevo: pasar una función como argumento a test(); no lo teorices, di "le entregamos al runner los pasos para que los ejecute cuando toque". El expect es su resultado esperado de toda la vida.
| Tú ves… | Playwright necesita… |
|---|---|
| "el campo de email" | page.fill("#email", …) — por su id (#) |
| "el botón azul de Login" | page.getByRole("button", { name: "Login" }) — por su rol visible |
| "el mensaje de bienvenida" | page.locator("#welcome") — apunta al elemento, listo para verificar |
Cada elemento de una página tiene "dirección": su selector. Encontrarla es destreza detective de QA — y la vas a practicar AHORA mismo.
Concepto puente: el robot no ve píxeles, ve el árbol HTML, y el selector es la dirección postal del elemento. Hoy usamos ids (#email) por simplicidad; menciona que getByRole es la práctica profesional moderna y la verán en la sesión hands-on. Transición directa al playground de inspección.
Acceso a tu cuenta
Y pasa el mouse sobre la mini-app.
Encuentra el selector del campo email, del password y del botón. Apúntalos: los usarás en la próxima slide.
Esto mismo se hace con DevTools (click derecho → Inspeccionar) o con el inspector de Playwright.
Momento lúdico: activa el modo inspección y deja que la audiencia te dirija. El tooltip naranja muestra el selector de cada elemento (#email, #password, #login-btn, #welcome). Pídeles anotar los selectores — en la siguiente slide los usan de verdad. La app también funciona: pueden tipear y hacer login manual (ana@qa.com / secreto123).
Acceso a tu cuenta
EL momento wow de la clase — no lo apures. Ejecuta el script tal cual: el cursor fantasma se mueve, tipea y clickea; el expect pasa en verde. Luego ROMPE el test en vivo: cambia la password a "incorrecta" y re-ejecuta → aparece el error y el banner FAILED. Lección: el test no "se rompió" — ENCONTRÓ algo. Eso es exactamente su trabajo. Invita a alguien a dictar otra variación (email vacío, expect del #error-msg…).
await page.fill("#email", "ana@qa.com")?fill = rellenar: primer argumento el selector (dónde), segundo el texto (qué). Verificar contenido sería expect(...).toHaveValue(...) — las acciones actúan, los expect verifican.Refuerza la distinción acciones (goto, fill, click — hacen cosas) vs aserciones (expect — verifican cosas). Un test = acciones que llevan la app a un estado + aserciones que comprueban el resultado esperado. Esa estructura es universal en cualquier framework de pruebas.
"Ana" — un valor
↳ const email — con nombre
↳ login(email, pass) — dentro de una función
↳ if / for — con lógica
↳ login.spec.ts — en un archivo
↳ tests/ — en una carpeta
↳ mi-proyecto/ — en un repositorio 🎉
Un repo es solo carpetas y archivos organizados. Nada de magia: cada nivel lo construiste hoy.
Cierre del arco del zoom-out: recorre la columna derecha de abajo hacia arriba o de arriba a abajo y muestra que CADA nivel lo construyeron en la sesión. Del árbol solo importan tres cosas: package.json (la cédula), tests/ (donde escriben) y config. node_modules se instala solo y no se toca. Spec = especificación: cada archivo .spec.ts especifica el comportamiento esperado de una feature.
$ npx playwright test Running 1 test using 1 worker ✓ login.spec.ts:3 › login exitoso (2.1s) 1 passed (2.5s)
✗ login.spec.ts:3 › login exitoso Error: expect(locator).toBeVisible() Locator: locator("#welcome") Expected: visible Received: hidden at login.spec.ts:8:48
El error te dice qué esperaba, qué encontró y en qué línea. Leer errores con calma = tu superpoder QA.
Desactiva el pánico al rojo: el error de Playwright es un reporte de bug bien escrito — expected vs received más la línea exacta. Ellos LEEN reportes de bugs todos los días; esto es lo mismo. Un test que falla puede significar bug encontrado (¡victoria!) o test mal escrito — distinguir ambos es el oficio que practicarán en hands-on.
test("guardar cambios", async ({ page }) => { page.click("#save-btn"); // ⚠️ algo falta aquí… await expect(page.locator("#saved-msg")).toBeVisible(); });
await, el test NO espera el click y corre directo al expect → el mensaje aún no existe → falla intermitente (flaky). Acabas de diagnosticar el bug más común de la automatización. Bienvenida/o al oficio. 🎓Quiz final integrador: diagnostican un bug REAL de código de automatización — el await faltante que produce flakiness. Si la mayoría acierta, la clase cumplió su objetivo. Conecta con el quiz 6: misma causa, ahora ELLOS la detectan en código ajeno.
VS Code + Node.js. 10 minutos, gratis.
npm init playwright@latest
y el árbol de la slide 32 aparece solo.
Siguiente etapa — Dojo Lab 1: tus primeros specs reales, en la Dojoteca. 🚀
Ir a los Decks del DOJO →
Hoy: valores → variables → funciones → lógica → TypeScript → async → tu primer test. Ya hablas el idioma.
Lee el puntaje grupal y celebra sin importar el número — el score mide participación, no aptitud. Cierra con el mapa del viaje completo en una frase. Anuncia fecha/formato de la sesión hands-on y los 2 prerequisitos (VS Code + Node instalados). Comparte este archivo HTML: los playgrounds funcionan offline y pueden re-practicar solos en casa.