Playwright: reorganización del framework — de archivos didácticos a estructura de producción
9 specs didácticos separados en learning/, 2 tests de producción en e2e/ con fixtures y POM. Refactor real: qué cambió, qué se rompió, resultado.
Contexto: por qué reorganizar ahora
Hasta el Post 10 Testing de grilla en Playwright, los tests vivían todos en la misma carpeta:
tests/
├── serenity/
│ └── auth.setup.ts
├── example.spec.ts
├── saucedemo.spec.ts
├── serenity-assertions.spec.ts
├── serenity-clientes-excel.spec.ts
├── serenity-data-driven.spec.ts
├── serenity-fixtures.spec.ts
├── serenity-grilla-interacciones.spec.ts
├── serenity-locators.spec.ts
└── serenity-storagestate.spec.ts
9 archivos .spec.ts, todos al mismo nivel. Cada uno enseña un concepto: locators, assertions, fixtures, storageState, data-driven, grilla. Eso tenía sentido mientras escribía la serie — un post, un archivo, un tema.
Pero como framework esto no funciona. Si alguien clona el repo y corre npx playwright test, le caen encima 9 specs didácticos mezclados, con nombres que no dicen nada sobre qué funcionalidad testean. No hay separación entre "estoy aprendiendo esto" y "esto es un test real".
Decidí separar.
La separación: learning/ vs e2e/
La idea es sencilla:
tests/learning/ — Los specs didácticos de la serie. Cada archivo corresponde a un post, muestra un concepto. Quedan como referencia y documentación. No corren por defecto.
tests/e2e/ — Los tests de producción. Usan todo lo aprendido: POM, fixtures, storageState, data-driven, assertions modernas. Son lo que correría en una empresa.

La diferencia no es solo de carpetas. Es de cómo se escriben.
Learning vs E2E: qué cambia en el código
Cómo se hacía en los learning specs
Cada test instanciaba los Page Objects manualmente:
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { DashboardPage } from '../pages/DashboardPage';
test('login válido', async ({ page }) => {
const loginPage = new LoginPage(page);
const dashboardPage = new DashboardPage(page);
await loginPage.goto();
await loginPage.login('admin', 'serenity');
await dashboardPage.verificarVisible();
});
Funciona. Pero hay repetición: cada test que necesite loginPage tiene que hacer new LoginPage(page). En 5 tests no molesta. En 50, sí.
Cómo se hace en los e2e specs
Los e2e usan los fixtures que armé en el Post 5. El Page Object viene inyectado:
import { test, expect } from '../../fixtures/test-fixtures';
test('login válido', async ({ loginPage, dashboardPage }) => {
await loginPage.goto();
await loginPage.login('admin', 'serenity');
await dashboardPage.verificarVisible();
});
La lógica del test es la misma. Lo que cambia es de dónde vienen loginPage y dashboardPage. En vez de crearlos manualmente, Playwright los inyecta a través del fixture system. Menos código repetido, más fácil de mantener.
Y otro detalle: los e2e usan baseURL del config. En vez de hardcodear page.goto('https://demo.serenity.is/'), hacen page.goto('/'). Si mañana cambio de entorno, cambio una línea en el config y no toco ningún test.
Los tests de producción
login.spec.ts
Combina lo que estaba en serenity-data-driven.spec.ts: login positivo y login negativo con data-driven.

5 tests: 2 positivos (login manual + click con credenciales precargadas) y 3 negativos data-driven (password incorrecta, usuario incorrecto, campos vacíos).
Todos usan test.use({ storageState: { cookies: [], origins: [] } }) para anular la autenticación automática — necesitamos probar el login real, no arrancar ya logueados.
clientes.spec.ts
Combina lo que estaba repartido en dos archivos separados (serenity-clientes-excel.spec.ts + serenity-grilla-interacciones.spec.ts). Ahora es un solo archivo organizado por describe:


4 secciones, 8 tests:
- Validación contra Excel — los 91 clientes campo a campo con soft assertions
- Ordenamiento — ID asc/desc, Empresa asc/desc
- Filtros Select2 — filtro por país, filtro combinado, limpiar filtros
- Búsqueda — buscar ALFKI, buscar texto inexistente
La navegación al módulo de Clientes está en beforeEach usando fixtures:
test.beforeEach(async ({ page, dashboardPage, clientesPage }) => {
await page.goto('/', { timeout: 15000 });
await dashboardPage.verificarVisible();
await dashboardPage.irAClientes();
await clientesPage.verificarVisible();
});
Cada test recibe clientesPage ya listo. No hay new ClientesPage(page) en ningún lado.
El config: una línea
El cambio en playwright.config.ts es mínimo:
export default defineConfig({
testDir: './tests',
testIgnore: ['**/learning/**'], // ← esta línea
// ... resto igual
});

testIgnore le dice a Playwright: todo lo que esté dentro de learning/ no lo corras. Así npx playwright test ejecuta solo los e2e y el auth.setup.
Los learning specs siguen en el repo. Siguen funcionando. Pero no corren a menos que los llames explícitamente.
El refactor: lo que tuve que ajustar
Mover los archivos fue la parte fácil. Lo que llevó un poco más de atención:
Imports rotos. Al mover los specs un nivel más abajo (de tests/ a tests/learning/), todos los paths relativos se rompieron. '../pages/LoginPage' pasó a ser '../../pages/LoginPage'. TypeScript los marca en rojo al instante, así que encontrar los rotos fue trivial. Arreglarlos fue mecánico pero había que hacerlo en los 9 archivos.
Path del Excel. El test de validación contra Excel usa path.join(__dirname, '..', 'test-data', ...). Al moverse un nivel, el path cambió: path.join(__dirname, '..', '..', 'test-data', ...). Esto aplica tanto al learning como al e2e.
Nada complejo. Pero si no lo hacés, nada compila.
Resultado: 34 passed, 3 failed

37 tests en total × 3 browsers:
- auth.setup → 1 passed
- login.spec.ts → 15 passed (5 tests × 3 browsers)
- clientes.spec.ts → 21 passed, 3 failed (8 tests × 3 browsers, el de Excel falla en los 3)

Login: 15/15. Los fixtures inyectan loginPage y dashboardPage correctamente. El data-driven genera un test por cada caso de credenciales inválidas. Todo funciona igual que en el learning spec.

Clientes: 21/24. Los 3 fallos son el test de validación contra Excel, uno por browser. Los mismos errores que ya documenté en el Post 9:
- EASTCs — el Excel tiene "EASTCs" con s minúscula, la grilla tiene "EASTC". Typo intencional en el dataset.
- FISSA — el Excel dice "Accounting Managers" (plural), la grilla dice "Accounting Manager" (singular). Otro typo intencional.
- CENTC — el Excel dice "Mexicos", la grilla dice "Mexico". Tercer typo intencional.
- WOLZA — el doble espacio en "Wolski Zajazd" del Excel. El bug real que Selenium nunca detectó y que Playwright sí encontró.
Los tests están funcionando exactamente como deben: detectan las discrepancias entre la fuente de datos y la aplicación.

Estructura final
PLAYWRIGHT-TYPESCRIPT-FRAMEWORK/
├── fixtures/
│ └── test-fixtures.ts
├── pages/
│ ├── ClientesPage.ts
│ ├── DashboardPage.ts
│ └── LoginPage.ts
├── test-data/
│ └── clientes-data.xlsx
├── tests/
│ ├── auth.setup.ts
│ │
│ ├── e2e/
│ │ ├── login.spec.ts
│ │ └── clientes.spec.ts
│ └── learning/
│ ├── example.spec.ts
│ ├── saucedemo.spec.ts
│ ├── serenity-assertions.spec.ts
│ ├── serenity-clientes-excel.spec.ts
│ ├── serenity-data-driven.spec.ts
│ ├── serenity-fixtures.spec.ts
│ ├── serenity-grilla-interacciones.spec.ts
│ ├── serenity-locators.spec.ts
│ └── serenity-storagestate.spec.ts
├── types/
│ └── Cliente.ts
├── utils/
│ └── excelReader.ts
└── playwright.config.ts

Los learning specs quedan como documentación viva de la serie. Cada archivo tiene su post correspondiente, el código funciona, los links del blog siguen apuntando a archivos que existen en el repo.
Los e2e specs son lo que importa: login.spec.ts y clientes.spec.ts combinan POM + fixtures + storageState + data-driven + soft assertions. Si alguien quiere ver cómo se ve un framework Playwright armado para producción, mira e2e/. Si quiere entender cómo se llegó ahí, lee learning/.
Estado actual
El framework tiene dos capas claras. Los tests de producción usan fixtures, baseURL, y agrupan por funcionalidad. Los didácticos quedan como referencia sin estorbar.
npx playwright test corre 37 tests en 3 browsers. 34 pasan. 3 fallan por discrepancias conocidas en el dataset de Excel.
Próximo post: ejecución paralela — cómo Playwright maneja workers y paralelismo, y cómo configurarlo.
—
🔗 Todo el código de esta serie está en: github.com/cesarbeassuarez/playwright-typescript-framework