# Cómo crear un framework de automatización de pruebas desde cero

**Categoría:** Testing y QA · **Nivel:** Avanzado · **Lectura:** 14 min
**Publicada:** 2026-07-29 · **Actualizada:** 2026-07-29 · **Autoría:** Underc0de
**Versión HTML (canónica):** https://underc0de.org/guias/testing/crear-un-framework-de-automatizacion-desde-cero/

## Respuesta rápida

Un framework de automatización no es una carpeta de scripts que crece sin orden: es una arquitectura en capas, cada una con una única responsabilidad. Arriba, los **tests** controlan el flujo del escenario y hacen las **aserciones**. En el medio, la capa de **objetos de página** (Page Object Model, un patrón que expone las acciones de cada pantalla y oculta sus selectores) ejecuta acciones e informa el estado, pero nunca verifica nada por sí misma. Abajo, capas de soporte: **configuración por entorno**, **datos de prueba**, el **driver o ejecutor** y los **reportes**. Esta arquitectura es la misma exista [Selenium, Cypress o Playwright](../selenium-cypress-playwright/index.md) detrás: lo que cambia con la herramienta es la implementación, no el diseño.

## Por qué armar un framework, no una carpeta de scripts

Cuando un equipo empieza a automatizar, agrega archivos de prueba a una carpeta compartida a medida que hacen falta. Funciona un tiempo. El problema aparece cuando la aplicación cambia: si un botón se movió de lugar, hay que corregir el mismo selector en quince archivos distintos. Ese síntoma —tocar diez lugares del código de pruebas por un solo cambio de interfaz— es la señal de que hace falta un **framework**, no más scripts sueltos.

Un framework de automatización es una arquitectura: capas con responsabilidades separadas, para que un cambio en la aplicación bajo prueba obligue a tocar un solo lugar del código de pruebas. No es una herramienta que se instala junto a Selenium, Cypress o Playwright: es el diseño que se construye alrededor de la que elijas para [ejecutar las pruebas](../selenium-cypress-playwright/index.md).

| Situación | Sin capas | Con framework en capas |
| --- | --- | --- |
| Cambia un selector | Se corrige en cada archivo | Se corrige en un objeto de página |
| Se migra el ejecutor | Se reescriben los tests | Se reemplaza la capa de drivers |
| Se agrega un ambiente nuevo | URLs a mano en cada archivo | Un archivo de configuración nuevo |
| Falla un test en CI | Nadie sabe por qué | El reporte dice qué pasó |
| Se suma alguien al equipo | Tarda en ubicarse | La estructura ya se lo dice |

En junio de 2020, en la sección Testing Automatizado del foro de Underc0de, **ZarathuxXxtrA** compartió el desarrollo de *Lippia*, un framework propio armado para un proyecto real: ahorro de tiempo, escalabilidad, bajo acoplamiento entre pruebas e interfaz, y ejecución en paralelo. Es el mejor ejemplo narrativo de por qué invertir en arquitectura en lugar de acumular scripts.

> **La lección incómoda**
>
> El mismo relato deja algo que conviene decir sin vueltas: **un framework propio no siempre es la mejor decisión**. Mantenerlo cuesta: hay que actualizarlo, documentarlo y sostenerlo cuando quien lo escribió se va. El repositorio esperado devolvió error 404 al verificarlo, señal de que quedó discontinuado: se lo menciona solo como ejemplo histórico, no como herramienta para adoptar hoy. Antes de escribir uno desde cero, evaluá si esta misma arquitectura sobre un ejecutor existente resuelve el problema con menos código para mantener.

## El principio que sostiene todo

La arquitectura completa se sostiene sobre una única regla, y es la que más se rompe en la práctica: **el test controla el flujo del escenario y hace las aserciones; el objeto de página ejecuta las acciones sobre la interfaz e informa el estado resultante, pero nunca verifica nada por sí mismo.**

Esta separación viene del **Page Object** (objeto de página), un patrón de diseño orientado a objetos que Selenium y Playwright documentan como buena práctica. Cada pantalla relevante se representa como una clase que expone métodos con lo que se puede hacer ahí —iniciar sesión, agregar un producto al carrito— y oculta hacia adentro los selectores del HTML. Si la interfaz cambia, se corrige un único método del objeto de página, y todos los tests que lo usan siguen funcionando.

![Diagrama de las capas de un framework de automatización. Arriba, tests y aserciones: controlan el flujo y deciden qué resultado es correcto. En el medio, objetos de página (Page Object Model): ejecutan las acciones sobre la interfaz e informan el estado, sin verificar nada. Abajo, cuatro capas de soporte: configuración por entorno, datos de prueba, drivers o ejecutor (Selenium, Cypress o Playwright) y reportes. Un recuadro destaca la regla de oro: las aserciones viven en el test, nunca en el objeto de página.](../../assets/img/guias/framework-automatizacion-capas.svg)

*Las capas de un framework de automatización: tests y aserciones arriba, objetos de página en el medio, y configuración, datos, drivers y reportes como soporte. Ninguna capa depende rígidamente de otra.*

El error más común, y el que vuelve inmantenible cualquier framework, es meter un `expect` dentro de un objeto de página: un mismo método termina sirviendo a escenarios que esperan resultados distintos, y se llena de parámetros o se duplica. La regla lo evita de raíz: el objeto de página informa —por ejemplo, devuelve el texto visible en pantalla— y el test decide si ese estado es correcto. En diciembre de 2023, **Mr. Bones** publicó en la sección QA del foro de Underc0de un desarrollo sobre el Page Object Model que explica esta doble cara: hacia el test, expone los servicios de la pantalla; hacia la implementación, es el único lugar que conoce su HTML.

## Estructura de carpetas concreta

Las capas del diagrama anterior se traducen en carpetas reales. No hace falta seguir una convención exacta —cada equipo adapta los nombres—, pero sí que cada carpeta tenga una única responsabilidad y que ningún archivo mezcle dos capas a la vez:

```text
proyecto-automatizacion/
├── tests/                   # Capa 1: tests y aserciones
│   ├── login.spec.ts
│   └── carrito.spec.ts
├── pages/                    # Capa 2: objetos de página
│   ├── base.page.ts
│   ├── login.page.ts
│   └── carrito.page.ts
├── config/                   # Configuración por entorno
│   ├── local.json
│   ├── staging.json
│   └── produccion.json
├── fixtures/                  # Datos de prueba
│   ├── usuarios.ts
│   └── productos.ts
├── reports/                    # Salida de reportes (no versionada)
├── playwright.config.ts
└── package.json
```

La correspondencia es directa: `tests/` es la capa 1, la única que decide qué es correcto. `pages/` es la capa 2: cada archivo representa una pantalla, y ninguno debería importar la librería de aserciones. `config/`, `fixtures/` y `reports/` son las capas de soporte: la primera resuelve contra qué entorno correr, la segunda genera los datos de cada test, la tercera recibe la salida de la ejecución. Un archivo `base.page.ts` suele centralizar lo común a todas las páginas para que el resto herede en lugar de repetir código.

## La capa de objetos de página, en código

Para que la regla no quede en la teoría, así se ve en código. El ejemplo usa [Playwright](../como-instalar-y-usar-playwright/index.md) (versión 1.62.0 al 29 de julio de 2026), pero la misma separación aplica igual con Selenium o Cypress: cambia la API, no el diseño.

```typescript
// pages/login.page.ts — capa de objetos de página: acciones y estado, sin aserciones
export class LoginPage {
  constructor(private page: Page) {}

  async ir() {
    await this.page.goto('/login');
  }

  async iniciarSesion(usuario: string, clave: string) {
    await this.page.fill('#usuario', usuario);
    await this.page.fill('#clave', clave);
    await this.page.click('#ingresar');
  }

  async mensajeDeError() {
    return this.page.textContent('.error-message');
  }
}

// tests/login.spec.ts — capa de tests: controla el flujo y hace las aserciones
test('rechaza una clave incorrecta', async ({ page }) => {
  const login = new LoginPage(page);
  await login.ir();
  await login.iniciarSesion('usuario@test.com', 'clave-incorrecta');

  expect(await login.mensajeDeError()).toBe('Usuario o contraseña incorrectos');
});
```

Notá que `mensajeDeError()` no decide nada: devuelve el texto y listo. Quien decide si es correcto es el test, con `expect`. Si mañana cambia lo que hay que verificar, el mismo método sirve sin tocarlo: solo cambia la aserción, que vive en el test.

## Configuración por entorno

La capa de configuración guarda todo lo que cambia según dónde corre la suite: la URL base, las credenciales de prueba y los tiempos de espera. Nada de esto debería estar escrito adentro de un test ni de un objeto de página —ahí es donde termina el clásico `http://localhost:3000` pegado en cincuenta archivos, imposible de cambiar cuando la suite corre contra staging.

Un archivo de configuración por entorno (local, staging, producción) resuelve esto con un mecanismo simple: la suite recibe una variable que indica cuál cargar, y a partir de ahí tests y objetos de página consultan siempre el mismo objeto de configuración, nunca un valor fijo. Cuando la suite corre en un pipeline de CI, esa variable suele venir inyectada desde los secretos del repositorio en lugar de un archivo versionado, algo que se retoma en la sección sobre integración con CI.

## Datos de prueba

Los datos que usa cada test —un usuario, un producto, un monto— son otra capa, separada de los tests y de los objetos de página. La alternativa más frágil es que dos tests usen el mismo usuario fijo de una base compartida: en serie funciona, pero en paralelo un test le cambia el estado a ese usuario mientras otro lo está usando, y aparecen fallos que no tienen que ver con ningún defecto real.

La solución habitual son **fixtures** o *builders*: funciones que generan datos propios para cada corrida, con un identificador único, en lugar de depender de un dato fijo compartido. Esta guía se concentra en dónde vive esa capa dentro del framework; el [cómo generar esos datos](../gestion-y-generacion-de-datos-de-prueba/index.md) —con qué herramientas, con qué estrategias de aislamiento— tiene su propia guía dedicada en el portal.

## Reportes

La capa de reportes no es un detalle cosmético: permite diagnosticar un fallo sin volver a correr la suite, algo clave cuando el fallo ocurrió en CI y la máquina donde corrió ya no existe.

Un reporte útil guarda, como mínimo, el resultado de cada test, una captura del momento del fallo y, si el ejecutor lo soporta, una traza con los pasos previos. Esta capa tampoco debería vivir mezclada con la de tests: lo habitual es que el propio ejecutor genere esta salida hacia una carpeta dedicada, sin agregar código de reporte adentro de cada test. Un test que falla de forma intermitente sin que el producto haya cambiado —un *flaky test*— suele detectarse revisando esos reportes en el tiempo; [cómo detectarlos y resolverlos](../como-detectar-y-solucionar-flaky-tests/index.md) tiene su propia guía en el portal.

## Integración con CI

Un framework bien separado en capas se integra a un pipeline de **integración continua** (CI, por sus siglas en inglés) casi sin fricción: cada capa ya sabe resolver su parte sin que nadie la configure a mano en la máquina donde corre. El flujo típico, sin entrar en la sintaxis de una herramienta en particular, es este:

1. **Instalar dependencias y el ejecutor.** Las mismas que se usan en desarrollo, fijadas a una versión concreta para que el pipeline sea reproducible.
2. **Resolver la configuración del entorno.** La capa de configuración toma sus valores de las variables que el pipeline inyecta, no de un archivo con datos fijos.
3. **Ejecutar el conjunto de tests.** La capa de tests corre igual que en una máquina local: el pipeline no necesita saber nada de objetos de página ni de selectores.
4. **Publicar el reporte como artefacto.** La capa de reportes guarda su salida en una carpeta que el pipeline conserva, aunque la máquina donde corrió se destruya después.

Este esquema es el mismo exista Selenium, Cypress o Playwright detrás: cambia el comando de ejecución y el formato del reporte, no las capas. El paso a paso completo para escribir el flujo de trabajo está en [pruebas automáticas con GitHub Actions](../pruebas-automaticas-con-github-actions/index.md); acá solo se muestra por qué la arquitectura en capas hace esa integración simple. Este diseño aplica sobre todo a pruebas de [integración o end-to-end](../pruebas-unitarias-integracion-y-end-to-end/index.md): las unitarias no necesitan objetos de página.

## Errores frecuentes

- **Elegir la herramienta antes que la arquitectura.** Decidir entre Selenium, Cypress y Playwright es más fácil y más barato de revertir que decidir cómo se van a organizar las capas.
- **Poner aserciones dentro de un objeto de página.** Rompe la regla central y hace que un mismo método sirva a escenarios que esperan resultados distintos.
- **Guardar datos de prueba dentro del objeto de página.** Los datos son una capa aparte; mezclarlos acopla la interfaz con valores que deberían poder cambiar solos.
- **Confundir un framework con una carpeta de scripts compartida.** Sin capas separadas, cualquier cambio en la interfaz obliga a tocar múltiples archivos.
- **Hardcodear URLs o credenciales en los tests.** Bypasea la capa de configuración y hace que la suite no pueda correr contra otro ambiente sin editar código.
- **Creer que un framework propio siempre es mejor.** Mantenerlo tiene un costo real y continuo; conviene evaluarlo antes de escribir la primera línea, no después.

## Preguntas frecuentes

**¿Por dónde empiezo a armar un framework propio?**
Empezá por la arquitectura, no por la herramienta: definí dónde van los tests con sus aserciones, los objetos de página, la configuración por entorno y los datos de prueba. Elegí un flujo crítico real —por ejemplo, iniciar sesión— y armá el camino completo para ese flujo: un objeto de página, un test que hace las aserciones, un archivo de configuración y un reporte. Recién con eso funcionando, sumá el segundo escenario reutilizando lo que ya existe. Empezar chico con las capas separadas desde el principio evita mover cien archivos después. La herramienta es secundaria: la arquitectura funciona igual detrás de Selenium, Cypress o Playwright.

**¿Qué capas debería tener un framework bien diseñado?**
Como mínimo, cinco: tests (controla el flujo y contiene las aserciones), objetos de página (ejecuta las acciones y conoce los selectores del HTML), configuración por entorno (URLs, credenciales y tiempos de espera que cambian entre local, staging y producción), datos de prueba (fixtures o builders con datos propios en lugar de una base compartida) y reportes (resultados, capturas y trazas para diagnosticar un fallo sin reproducirlo a mano). Estas capas no dependen rígidamente entre sí: se puede cambiar el ejecutor sin tocar los datos, o sumar un ambiente sin tocar los reportes. Esa independencia es la que permite que el framework crezca sin volverse inmantenible.

**¿El Page Object Model es obligatorio?**
No en sentido estricto, pero es el patrón que casi todos terminan reinventando apenas la suite crece más allá de un puñado de tests. Escribir los selectores dentro de cada test funciona con pocos escenarios, pero se vuelve insostenible cuando la interfaz cambia seguido, porque un mismo botón puede estar referenciado en decenas de archivos. El Page Object Model separa lo que un test necesita saber (qué acción ejecutar) de cómo se ejecuta en el HTML real. Selenium y Playwright lo documentan como buena práctica, y el foro de Underc0de tiene un desarrollo dedicado que lo explica. Conviene sumarlo antes de que la duplicación de selectores se vuelva un problema real.

**¿Cómo integro el framework con CI/CD?**
Depende de que las capas ya estén separadas: la configuración tiene que recibir sus valores desde variables de entorno, no de datos escritos a mano, y los reportes tienen que guardarse en una carpeta que el pipeline conserve como artefacto. Con eso resuelto, el flujo típico es instalar dependencias y ejecutor, resolver la configuración del pipeline, ejecutar los tests y publicar el reporte, aunque la máquina donde corrió ya no exista. Esta guía se concentra en el diseño de las capas que hacen posible esa integración; el paso a paso para escribir el flujo de trabajo está en la guía de pruebas automáticas con GitHub Actions del portal.

**¿Conviene construir un framework propio o usar uno existente?**
Depende del costo de mantenerlo, no solo del de construirlo. Tiene sentido cuando el proyecto tiene necesidades muy particulares que ninguno existente resuelve bien, o cuando el equipo tiene la capacidad técnica y el tiempo para sostenerlo. El relato de Lippia, compartido en el foro de Underc0de en 2020, muestra las ventajas reales que puede traer: ahorro de tiempo, escalabilidad y ejecución en paralelo. Pero también deja una lección honesta: construirlo es la parte fácil; mantenerlo cuando cambian las dependencias y quien lo escribió se va es la parte cara. Antes de empezar uno desde cero, evaluá si un ejecutor existente (Selenium, Cypress o Playwright) con esta misma arquitectura de capas no resuelve el problema con mucho menos código para mantener.

**¿Cómo organizo los datos de prueba dentro del framework?**
Los datos de prueba viven en su propia capa, separados de los tests y de los objetos de página: ni un test debería tener datos escritos a mano en sus pasos, ni un objeto de página debería generarlos. Lo habitual es usar fixtures o builders que generan datos propios y aislados para cada corrida, de forma que dos tests en paralelo no choquen usando el mismo usuario. Esto evita un problema frecuente en suites grandes: pruebas que fallan de forma intermitente porque comparten datos con otra que corrió al mismo tiempo. El portal tiene una guía dedicada a la gestión y generación de datos de prueba; acá alcanza con dejar claro que es una capa más, con su propio lugar en la estructura de carpetas.

## Fuentes

Documentación oficial y aportes de la comunidad consultados para esta guía. Fecha de consulta: 29 de julio de 2026.

### Aportes de la comunidad Underc0de

1. **Underc0de, foro.** [POM (Modelo de Objetos de Página)](https://underc0de.org/foro/qa-testing/pom-modelo-de-objetos-de-pagina/), por *Mr. Bones*, 18 de diciembre de 2023, sección QA (Quality Assurance). Explica el patrón que sostiene la capa de objetos de página de esta guía.
2. **Underc0de, foro.** [Lippia, un Framework Multipropósito](https://underc0de.org/foro/testing-automatizado/lippia-un-framework-multiproposito/), por *ZarathuxXxtrA*, 1 de junio de 2020, sección Testing Automatizado. Relato de por qué un equipo construyó un framework propio y de lo que cuesta mantenerlo; usado acá solo como ejemplo histórico, sin recomendar el proyecto como herramienta vigente.

### Documentación oficial

3. **Selenium.** [Page object models](https://www.selenium.dev/documentation/test_practices/encouraged/page_object_models/). Definición y buenas prácticas del patrón desde la documentación oficial de Selenium.
4. **Playwright.** [Page object models](https://playwright.dev/docs/pom). Implementación del patrón con la sintaxis de Playwright.
5. **Playwright.** [Best practices](https://playwright.dev/docs/best-practices). Recomendaciones oficiales sobre estructura y mantenimiento de suites.

## Guías relacionadas

- [Selenium, Cypress o Playwright](../selenium-cypress-playwright/index.md)
- [Gestión y generación de datos de prueba](../gestion-y-generacion-de-datos-de-prueba/index.md)
- [Pruebas automáticas con GitHub Actions](../pruebas-automaticas-con-github-actions/index.md)
- [Cómo detectar y solucionar flaky tests](../como-detectar-y-solucionar-flaky-tests/index.md)
- [Índice de Testing y QA](../index.md)

**URL canónica:** https://underc0de.org/guias/testing/crear-un-framework-de-automatizacion-desde-cero/
