De clases sueltas a una arquitectura de pruebas.
Page Object · Fixtures · Paralelización · KATA — los patrones que sostienen todo framework profesional.
Sesión de patrones. Prerequisito: ya saben clases, herencia y un Page Object básico (sesión Coding Classes). Hoy subimos un escalón: por qué existen los patrones, qué problema resuelve cada uno, y cómo se combinan en KATA — el framework real de este repo. Todo lo ejecutamos en vivo: live cells con JS real, un simulador de Page Object que mueve el cursor, un simulador de workers en paralelo, y un trazador de capas KATA. Que la sala TOQUE cada widget — la interacción es el punto.
JS moderno · clases + extends · un Page Object que envuelve una página · tu primer test() ejecutado.
Los 4 patrones que escalan de 5 a 5.000 tests sin que el proyecto colapse: POM a fondo · Fixtures · Paralelización · KATA.
// Un patrón = una respuesta probada // a un dolor que SIEMPRE reaparece: selector cambió → Page Object setup repetido → Fixtures suite lentísima → Paralelización todo junto, a escala → KATA
Cada acto: primero el dolor, luego el patrón que lo cura. Nunca sintaxis sin motivo.
Encuadre central de la sesión: un patrón NO es decoración, es la respuesta consensuada de la industria a un dolor recurrente. La columna derecha mapea dolor→patrón — es el índice de la charla. Promesa pedagógica: cada acto abre con el dolor concreto (mostrado en código), para que el patrón se sienta inevitable, no impuesto. Si alguien pregunta "¿por qué no escribir el test y ya?": funciona con 5 tests; a 500, sin patrones, el proyecto se vuelve inmantenible. Eso es lo que vamos a vivir.
class donde viven los selectores y las acciones de UNA página. Los tests la usan sin tocar selectores. Hoy lo llevamos más lejos — y descubrimos que es solo el primero de cuatro patrones que se apilan.Calentamiento que reactiva POM y fija el vocabulario. Si la sala falla esto masivamente, dedica 3 minutos a repasar el Page Object de la sesión anterior antes de seguir — el resto de la charla se apoya en este concepto. La explicación planta la semilla de "cuatro patrones apilados", que es el arco completo.
test("login exitoso", async ({ page }) => { await page.fill("#email", "ana@qa.com"); ┐ await page.fill("#password", "secreto123"); │ mismos selectores, await page.click("#login-btn"); ┘ copiados en cada test await expect(page.locator("#welcome")).toBeVisible(); }); test("login inválido", async ({ page }) => { await page.fill("#email", "ana@qa.com"); … otra vez … await page.fill("#password", "malamala"); await page.click("#login-btn"); await expect(page.locator("#error-msg")).toBeVisible(); }); // 50 tests más… y mañana el dev renombra #login-btn 💀
Pregunta asesina: si #login-btn cambia de id, ¿cuántos lugares corriges? Con 50 tests → 50 ediciones y un riesgo de error en cada una.
Deja que ELLOS nombren el problema: pasos duplicados + selectores dispersos. La pregunta del "renombre" es el gancho de todo el acto — sostén la incomodidad unos segundos antes de revelar la cura. Conecta con un dolor que ya conocen: test cases manuales copiados. La herramienta para encapsular ya la tienen: una clase.
class LoginPage { constructor(page) { this.page = page; // el "control remoto" } // selectores: una sola fuente email = () => this.page.locator("#email"); password = () => this.page.locator("#password"); submit = () => this.page.locator("#login-btn"); async login(email, password) { await this.email().fill(email); await this.password().fill(password); await this.submit().click(); } }
Cada selector vive en UN getter. ¿Cambió #login-btn? Editas 1 línea — los 50 tests ni se enteran.
login() cuenta una historia: llena, llena, clic. El test lee como un caso manual.
Es la clase de siempre: constructor · this · método async · await. POM = clase + propósito.
Subimos del POM básico: aquí los selectores se centralizan en getters arrow (this.page.locator). Regla profesional que verán en KATA: un locator usado en 2+ acciones se extrae a un getter; usado una vez, va inline. No filosofar todavía — la siguiente slide muestra la regla acción-vs-aserción, y luego lo ejecutan en vivo. Suelta el término oficial: Page Object Model, patrón estándar de industria.
class LoginPage { async login(email, pass) { await this.email().fill(email); await this.password().fill(pass); await this.submit().click(); } // hace cosas, no juzga }
test("login exitoso", async ({ page }) => { const login = new LoginPage(page); await login.login("ana@qa.com", "secreto123"); await expect(page.locator("#welcome")) .toBeVisible(); // aquí se decide });
Misma acción login() sirve para el caso feliz y el inválido — solo cambia el expect que pone el test. Reutilización máxima, decisión en un solo lugar visible. Esta división reaparece idéntica en KATA.
Distinción que separa el POM amateur del profesional: las acciones (efectos) viven en el Page Object; las aserciones de negocio (el veredicto) viven en el test. Matiz honesto: aserciones FIJAS e inevitables (un redirect obligatorio, status 201) sí pueden ir dentro de la acción como parte de "la acción terminó bien"; las aserciones de NEGOCIO van fuera. Esta misma regla es la Regla 6 de KATA (fixed assertions inside, flow assertions outside) — anúncialo, es un puente directo.
Acceso a tu cuenta
Este simulador EJECUTA el JavaScript de verdad: la clase LoginPage corre tal cual correría en Playwright contra un page falso que mueve el cursor. Tres demos en vivo: (1) ejecutar tal cual → PASS verde; (2) cambiar password a "malamala" → FAIL con expected/received legible; (3) el experimento estrella: dentro de la clase, cambiar "#login-btn" por "#boton-entrar" → error "No existe el elemento" → corregir EN UN SOLO LUGAR → verde. Ese momento ES el argumento del Page Object, vivido en carne propia.
#login-btn → #signin-btn. Tienes 50 tests que hacen login con LoginPage. ¿Cuántos lugares corriges?login.login(...) y no se enteran del cambio. La opción B es el optimista mágico — ninguna herramienta adivina un renombre.Si hicieron el experimento del selector en el simulador, este quiz formaliza lo que VIVIERON. Respuesta correcta masiva = acto cumplido. Transición: "POM resuelve los selectores… pero todavía cada test escribe new LoginPage(page) y prepara su propio setup. Ese es el siguiente dolor: las Fixtures."
test("ve su dashboard", async ({ page }) => { const login = new LoginPage(page); ┐ await login.goto(); │ el MISMO await login.login(EMAIL, PASS); │ ritual de const dash = new DashboardPage(page); ┘ arranque… await dash.expectVisible(); }); test("edita su perfil", async ({ page }) => { const login = new LoginPage(page); … copiado … await login.goto(); await login.login(EMAIL, PASS); // y recién aquí empieza el test real 😩 });
Construir objetos, navegar, autenticar — antes de cada test. Ruido que tapa lo que el test realmente prueba.
¿Quién cierra sesión, borra el usuario creado, resetea el estado después? Hoy: nadie, o copiado en cada test.
Que alguien prepare el escenario antes y lo limpie después, y le entregue al test solo lo que pidió, listo para usar. Eso es una Fixture.
El dolor de las fixtures tiene DOS caras: el setup repetido (arriba) y el teardown olvidado (limpieza). Subraya ambas — la limpieza es la que casi nadie hace bien a mano. La idea de fixture = "preparar antes + entregar + limpiar después" cubre las dos. Pista que ya conocen: ({ page }) YA es una fixture que Playwright les da preparada. Hoy aprenden a fabricar las suyas.
test("login", async ({ page }) => { // ▲ // esto YA es una fixture await page.goto("/login"); });
Playwright construye un page fresco antes de tu test, te lo presta, y lo desecha después. Tú nunca escribiste new Page() ni lo cerraste. Eso es una fixture trabajando.
Un recurso listo para usar que el framework arma, te inyecta por nombre, y limpia solo. Inyección de Dependencias (DI) aplicada a tests.
page · request · context · browser. Pides por nombre lo que usas; lo demás ni se crea.
Crear las tuyas con test.extend: una fixture loginPage, una authedUser… que lleguen ya preparadas a cada test.
El "¡ahhh!" del acto: ({ page }) siempre fue una fixture. Define fixture en términos llanos: recurso listo, inyectado por nombre, auto-limpiado. Menciona el término técnico (Dependency Injection) una vez, sin enredarte — lo importante es la intuición. El destructuring ({ page }) que ya dominan es exactamente cómo se piden las fixtures: por nombre, desempacando solo lo necesario.
import { test as base } from "@playwright/test"; export const test = base.extend({ // fixture llamada "loginPage" loginPage: async ({ page }, use) => { // 1 · SETUP (antes del test) const login = new LoginPage(page); await login.goto(); // 2 · entregar al test await use(login); // 3 · TEARDOWN (después del test) await page.close(); }, });
useTodo lo que escribes arriba de use() corre como preparación, una vez por test que la pida.
await use(value)La frontera. value es lo que el test recibe en ({ loginPage }). El test corre durante esta línea.
useTodo lo de abajo es limpieza: cerrar, borrar, resetear. Corre siempre, incluso si el test falla.
EL concepto técnico del acto: la firma async ({ deps }, use) => {} y el papel partidor de await use(). Mensaje clave: use() NO es "ejecuta el código", es "PAUSA aquí y entrégale el control al test; cuando el test termine, sigue con la limpieza de abajo". Es un sándwich: setup arriba, test en el medio (use), teardown abajo. La siguiente slide lo muestra paso a paso para que vean el control saltando dentro y fuera del test. El teardown corre aunque el test falle — garantía crítica que a mano se pierde.
— en reposo —
La slide que vuelve tangible la mecánica de use(). Pausa en el paso 4 (await use → el control SALTA al cuerpo del test, líneas 8-10) y en el paso 5 (el test terminó → el control VUELVE a la línea de abajo, page.close). Mensaje a martillar: el test ocurre "dentro" de await use(); por eso el teardown de abajo siempre se ejecuta al final. Hazlo dos veces si hace falta: este modelo mental es lo que confunde a casi todos al principio.
// pide solo "api" → NO abre navegador test("GET /orders", async ({ api }) => { await api.orders.getOrders({ limit: 10 }); }); // pide "ui" → SÍ abre navegador test("ve la lista", async ({ ui }) => { await ui.orders.navigateTo(); });
Playwright instancia solo las fixtures que el test nombra. Tests de API puros no pagan el costo de un navegador.
base.extend({ // authedUser DEPENDE de api authedUser: async ({ api }, use) => { const user = await api.users.create(); await api.auth.login(user); await use(user); await api.users.remove(user.id); // limpia }, });
Una fixture pide otras en su ({ ... }). Playwright resuelve la cadena y el orden de limpieza por ti.
Estos dos poderes son el motor de KATA: { api } sin navegador, { ui } con navegador, { test } combinando ambos — fixtures que dependen de fixtures.
Dos propiedades que justifican toda la inversión en fixtures: (1) Lazy — pedir solo { api } literalmente no abre navegador; es un ahorro de tiempo enorme en suites de API. Dato verídico del repo: la TestFixture expone api/ui/test y Playwright las instancia perezosamente. (2) Composición — una fixture lista otras en su destructuring y Playwright ordena setup y teardown en cascada. La callout conecta directo con KATA, que veremos en el acto 4: { api }, { ui }, { test } son exactamente estas fixtures combinadas.
await use(value)?use() parte la fixture en dos: lo de arriba es setup, lo de abajo es teardown, y el test corre en medio. Por eso la limpieza ocurre siempre al final — incluso si el test falla. Es el sándwich setup → use → teardown.Valida EL concepto del acto. Si vieron el stepper, esto es cosecha directa. Opción C caza a quien confunde use con una validación. Cierre del acto: "POM ordenó los selectores, las fixtures ordenaron el setup/teardown… pero la suite sigue corriendo un test tras otro. Lentísima. El siguiente patrón ataca el RELOJ: paralelización."
// Secuencial: la suma de TODO test 1 ████████ 8s test 2 ██████ 6s test 3 ████████ … ⌛ total = 8+6+...+N = la suite completa, sumada
En CI, una suite secuencial de 300 tests bloquea cada PR media hora. El feedback lento mata el shift-left.
Tu máquina tiene 8 núcleos y usas 1. Los otros 7 miran cómo un solo test corre a la vez.
Repartir los tests entre varios workers (procesos) que corren a la vez. El reloj de pared baja al del worker más cargado, no a la suma.
Para correr en paralelo, los tests deben ser independientes: sin estado compartido, sin depender del orden.
El dolor de la paralelización es el reloj de pared y se siente en CI. Encuadre del recurso desperdiciado: 8 núcleos, 1 en uso. La idea — workers en paralelo — y el requisito no negociable — independencia de tests — van juntos: el paralelismo es gratis SOLO si los tests no comparten estado. Eso conecta con la regla de independencia de KATA (cada test genera su propia data con faker). El simulador de la siguiente slide hace visible y tocable el ahorro.
Playwright lanza varios procesos de Node. Cada uno toma tests del lote y los corre. Por defecto reparte tests a ~½ de tus núcleos.
Cada worker tiene su propio navegador y sus propias fixtures. Lo que pasa en un worker no toca a otro — por eso la independencia importa.
Playwright corre archivos distintos en paralelo; los tests dentro de un archivo, en orden. fullyParallel: true paraleliza también dentro del archivo.
// playwright.config.ts export default defineConfig({ // nº de procesos en paralelo workers: process.env.CI ? 4 : undefined, // paraleliza tests DENTRO de cada archivo fullyParallel: true, }); // control fino por bloque: test.describe.configure({ mode: "parallel" }); test.describe.configure({ mode: "serial" });
workers = cuántos procesos. fullyParallel = si los tests de un mismo archivo también se reparten.
Modelo mental correcto y verídico: worker = proceso de Node con su propio navegador. Por defecto Playwright usa ~la mitad de los núcleos y paraleliza a nivel de ARCHIVO (no de test) salvo que pongas fullyParallel: true. mode "serial" fuerza orden dentro de un describe (úsalo solo cuando los tests genuinamente dependen entre sí — antipatrón a evitar). Datos del config son reales de Playwright. La siguiente slide lo vuelve interactivo: que la sala elija 1/2/4 workers y vea el reloj bajar.
El widget estrella de la paralelización. Demo: arranca en 2 workers, ejecuta → reloj ~8s. Cambia a 4 → reloj ~6s, speedup ~2.5×. Cambia a 1 → reloj 15s, speedup 1×. Dos verdades que la sala debe ver: (1) el trabajo total de CPU NO cambia — 15s de cómputo siempre; lo que baja es el reloj de PARED. (2) La aceleración no es lineal: 4 workers no dan 4× porque el archivo más largo (4s) marca un piso — eso es balanceo de carga, no magia. Honestidad: con 6 archivos y un archivo de 4s, ningún número de workers baja de 4s. Gran momento para nombrar el límite real.
# máquina 1 de 3 en CI npx playwright test --shard=1/3 # máquina 2 de 3 npx playwright test --shard=2/3 # máquina 3 de 3 npx playwright test --shard=3/3
Workers = paralelo dentro de una máquina. Shards = paralelo entre varias máquinas de CI, y luego se fusionan los reportes.
// playwright.config.ts (real) fullyParallel: false, retries: 0, // "Single worker for now - increase // when tests are stable" workers: 1,
Primero determinismo, después velocidad. Con la suite joven se corre en serie para cazar dependencias ocultas; el paralelismo se sube cuando los tests ya son sólidos.
Dos cierres del acto. Sharding: el siguiente nivel arriba de workers — distribuye entre MÁQUINAS de CI con --shard=i/n y fusiona reportes (blob report + merge-reports). Y la honestidad del repo: el playwright.config.ts REAL trae workers:1, fullyParallel:false, retries:0 con el comentario "increase when tests are stable". Es una decisión deliberada, no un olvido: primero garantizas que los tests son deterministas e independientes, y SOLO entonces subes workers. Enseña el principio: paralelizar tests frágiles multiplica la flakiness, no la velocidad útil.
Refuerza la distinción que el simulador hizo visible: trabajo total (constante) vs reloj de pared (lo que baja). Opción A es el malentendido más común. Si en el simulador notaron que 4 workers no daban 4×, este quiz lo formaliza. Transición al cierre: "POM, fixtures y paralelización… ¿cómo se viven los tres juntos, a escala, en un proyecto real? Eso es KATA."
KATA (Component Action Test Architecture) no inventa nada nuevo: organiza lo que ya viste en una arquitectura de 4 capas, donde cada capa extends la anterior.
Tu LoginPage es un componente de dominio. Ahí viven las ATCs.
{ api }, { ui }, { test } inyectan los componentes ya listos.
La misma playwright.config.ts: workers, projects, dependencias.
// La cadena de herencia REAL del repo class TestContext { ... } // L1 class UiBase extends TestContext // L2 class LoginPage extends UiBase // L3 ⟵ tu POM // y la fixture los junta: class UiFixture { login = new LoginPage(options); }
El extends que dominaste es el esqueleto. KATA es Page Objects + Fixtures con capas y nombres.
Tesis del acto: KATA no es un quinto concepto, es la SÍNTESIS de los tres anteriores en una estructura. Mapea explícito: POM=componentes (capa 3), fixtures=capa 4, paralelización=config. La cadena TestContext → UiBase → LoginPage es real y verídica del repo (kata-architecture.md). Mensaje que baja el miedo: si entendiste extends, fixtures y POM, ya entiendes el 90% de KATA — solo falta el vocabulario y el orden de las capas, que viene ahora.
Regla única: una capa superior puede usar una inferior, nunca al revés. Tu Page Object (L3, estrella) es el corazón — todo lo demás existe para servirlo o consumirlo.
El mapa de KATA, verídico (kata-architecture.md §1). De abajo arriba: TestContext (utilidades globales), Bases (helpers HTTP/Playwright), Componentes/ATCs (la estrella, tu POM), Steps (cadenas reutilizables para precondiciones, opcional), Fixtures (DI). La regla de direccionalidad (superior usa inferior, nunca al revés) es la que mantiene el orden. Marca la capa estrella: es donde el equipo pasa el 90% del tiempo escribiendo ATCs. El resto del andamiaje ya viene hecho en el boilerplate.
class LoginPage extends UiBase { @atc("PROJ-101") async loginSuccessfully(creds) { await this.fillAndSubmit(creds); await this.page.waitForURL( url => !url.pathname.includes("/login")); await expect(this.page) .not.toHaveURL(/.*\/login.*/); } }
Acceptance Test Case: no un clic suelto, sino un mini-flujo entero (precondición → acción → aserción fija).
@atc("PROJ-101")El decorador ata la ATC a su ticket de Jira 1:1. Da trazabilidad y tracing automático. Es un método con etiqueta.
Una ATC nunca llama a otra ATC. ¿Cadenas reutilizables? Eso es un Step (capa 3.5).
La ÚNICA pieza realmente nueva de KATA respecto a lo que ya saben: la ATC y el decorador @atc. Código real, adaptado del LoginPage del repo (PROJ-101, loginSuccessfully). Define ATC: caso de prueba completo (mini-flujo), no una interacción. El decorador @atc('TICKET') la liga a Jira 1:1 — trazabilidad. Reglas duras a mencionar sin agobiar: ATC atómica (no llama a otra ATC), aserciones fijas dentro / de negocio fuera (la regla del acto 1, ahora con nombre KATA). Un decorador es solo "un método con una etiqueta que añade comportamiento" — no te metas en la mecánica de decorators aquí.
— en reposo —
Cierra el círculo: muestra que una sola línea de test (ui.login.loginSuccessfully) atraviesa las 4 capas. Paso 1: el test pide la fixture { ui } (L4). Paso 2: la fixture ya tiene LoginPage instanciada (L4→L3). Paso 3: loginSuccessfully es la ATC en LoginPage (L3). Paso 4: usa helpers de Playwright heredados de UiBase (L2). Paso 5: que a su vez heredan config/faker de TestContext (L1). Mensaje: el test escribe UNA línea legible; las 4 capas hacen el trabajo pesado por debajo. Eso es la arquitectura pagando dividendos.
| Tipo de test | Fixture | ¿Navegador? | Cuándo |
|---|---|---|---|
| API pura (integration) | { api } | No | Testing de API. Default en tests/integration/ |
| Solo UI | { ui } | Sí | Flujo de UI sin setup por API |
| Híbrido | { test } | Sí | Setup por API + acción UI + verificación API |
| Precondiciones repetidas | { steps } | Depende | 3+ ATCs repetidas en 3+ archivos |
// API pura — NO abre navegador (lazy) test("PROJ-7: lista pedidos", async ({ api }) => { await api.orders.getOrders({ limit: 10 }); });
// Híbrido — crea por API, verifica por UI test("PROJ-9: aparece en lista", async ({ test }) => { const [, o] = await test.api.orders.create(data); await test.ui.orders.verifyVisible(o.id); });
Cierre operativo y verídico (kata-architecture.md §7): la tabla de selección de fixture es decisión diaria del equipo. La lazy-init del acto 2 aquí es regla de oro: { api } no abre navegador → suites de API muchísimo más rápidas. El { test } híbrido es la joya: crea estado por API (rápido) y verifica por UI (realista), compartiendo el mismo contexto. Estos snippets son el patrón real del repo. Con esto, el alumno ya sabe LEER cualquier test del repo y ELEGIR su fixture.
GET /orders, sin tocar UI. ¿Qué fixture pides?{ api }: como las fixtures son lazy, pedir solo api NO arranca un navegador — el test corre en milisegundos. Pedir { ui } o { test } "por si acaso" desperdicia segundos de arranque de browser en cada test. Pide lo mínimo que uses.Integra lazy-init (acto 2) + selección de fixture (acto 4). La respuesta correcta exige recordar que { api } no abre navegador. Opciones B y C son el antipatrón "pide de más por si acaso", que en una suite grande cuesta minutos. Si aciertan masivo, la sesión está cumplida: saben elegir fixture con criterio de costo.
El quiz que amarra el arco completo de la sesión: dolor→patrón. Es la tabla de la slide 2, ahora como evaluación. Respuesta correcta = el alumno internalizó que los patrones no son moda, son respuestas a dolores específicos. KATA cierra como el meta-patrón que los organiza. Tras este quiz, celebra y pasa al score final.
tests/
components/
TestContext.ts // L1
ui/UiBase.ts // L2
ui/LoginPage.ts // L3 · POM + ATCs
api/ApiBase.ts // L2
steps/*.ts // L3.5 · Steps
TestFixture.ts // L4 · Fixtures
UiFixture.ts // L4
e2e/** // tests UI (+API)
integration/** // tests API puros
playwright.config.ts // workers, projects
El 90% del tiempo: escribir ATCs en components/ui/ y components/api/, y orquestarlas en e2e/ · integration/.
TestContext, las Bases y las Fixtures vienen en el boilerplate. Heredas helpers; no los reescribes.
Skill /test-automation + references/kata-architecture.md: reglas completas de ATC, fixtures y Steps.
Aterriza todo en la estructura REAL de carpetas del repo (verídica). Mensaje tranquilizador: las capas bajas ya están construidas en el boilerplate; el alumno trabaja sobre todo en componentes (ATCs) y tests. Punteros reales para seguir: la skill /test-automation y references/kata-architecture.md tienen las 10 reglas de ATC, la tabla de fixtures y el módulo Steps al detalle. Esta slide es el puente del aprendizaje a la práctica del lunes.
| Patrón | Dolor que cura | Pieza clave | En KATA |
|---|---|---|---|
| 🎁 Page Object | selectores regados | class = una página | Componentes (L3) |
| 🔧 Fixtures | setup/teardown repetido | test.extend + use() | Fixtures (L4) |
| ⚙️ Paralelización | suite lentísima | workers aislados | config + projects |
| 🥋 KATA | todo junto, a escala | 4 capas + ATC | el marco completo |
Cruzaste la frontera entre escribir tests y diseñar una arquitectura de pruebas. Lo mismo que hace un SDET senior, ahora con nombre y forma.
El recap de una mirada: tabla que cruza patrón × dolor × pieza × lugar en KATA. Pídeles que la fotografíen o péguenla en Confluence — es el resumen portátil de la sesión. Nombra el logro exacto: pasaron de "escribir tests" a "diseñar arquitectura de pruebas", que es el salto de QA a SDET. Prepara el score final.
Este archivo corre offline: vuelve al simulador POM, al de workers y a los steppers. Toca todo.
Abre tests/components/ui/LoginPage.ts del repo y ubica: capa, ATC, decorador @atc, aserciones fijas.
Escribe tu primera ATC real con /test-automation — Plan → Code → Review sobre KATA.
Hoy: POM a fondo → Fixtures (use()) → Paralelización (workers) → KATA. De script a arquitectura.
Celebra el score y nombra el logro: hoy entendieron los cuatro patrones que sostienen cualquier framework profesional, vividos en vivo, no memorizados. Reto puente sin computadora: abrir el LoginPage.ts real e identificar cada concepto en código de producción. Próximo paso accionable: la skill /test-automation para escribir su primera ATC real. Comparte el archivo HTML — funciona offline para repasar.
POM · Fixtures · Paralelización · KATA.
Cuatro patrones, una forma de pensar las pruebas.
Slide de cierre y espacio para preguntas. Reabre el simulador de workers o el stepper de use() si alguien quiere ver algo otra vez — son la mejor herramienta para resolver dudas en vivo. Punteros finales: la skill /test-automation y la referencia de arquitectura KATA. Gracias y a escribir ATCs.