← Volver al listado de tecnologías
Capítulo 7: Page Object Model
Page Object Model
El patrón POM encapsula la lógica de interacción con páginas en clases reutilizables.
¿Por qué POM?
- Mantenibilidad: Cambios en UI en un solo lugar
- Reutilización: Métodos compartidos entre tests
- Legibilidad: Tests más expresivos
- Abstracción: Oculta detalles de implementación
Estructura Básica
// pages/login.page.ts
import { Page, Locator } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Contraseña');
this.submitButton = page.getByRole('button', { name: 'Iniciar' });
}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
}
Uso en Tests
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/login.page';
test('login exitoso', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('[email protected]', 'password123');
await expect(page).toHaveURL('/dashboard');
});
Ejemplo: TodoMVC
// pages/todo.page.ts
import { Page, Locator } from '@playwright/test';
export class TodoPage {
readonly page: Page;
readonly newTodoInput: Locator;
readonly todoItems: Locator;
constructor(page: Page) {
this.page = page;
this.newTodoInput = page.getByPlaceholder('What needs to be done?');
this.todoItems = page.locator('.todo-list li');
}
async goto() {
await this.page.goto('https://demo.playwright.dev/todomvc');
}
async addTodo(text: string) {
await this.newTodoInput.fill(text);
await this.newTodoInput.press('Enter');
}
async getTodoCount() {
return this.todoItems.count();
}
}
Ejercicio Práctico
Objetivo
Crear un Page Object para TodoMVC con métodos para completar y eliminar tareas.
Pasos
- Crea
pages/todo.page.ts - Agrega método
completeTodo(index) - Agrega método
deleteTodo(index) - Crea un test que use estos métodos
Ver solución
// pages/todo.page.ts
export class TodoPage {
// ... constructor anterior ...
async completeTodo(index: number) {
await this.todoItems.nth(index)
.getByRole('checkbox').check();
}
async deleteTodo(index: number) {
await this.todoItems.nth(index).hover();
await this.todoItems.nth(index)
.getByRole('button', { name: 'Delete' }).click();
}
}
// tests/todo.spec.ts
test('completar y eliminar', async ({ page }) => {
const todoPage = new TodoPage(page);
await todoPage.goto();
await todoPage.addTodo('Tarea 1');
await todoPage.completeTodo(0);
await todoPage.deleteTodo(0);
expect(await todoPage.getTodoCount()).toBe(0);
});
Criterios de Éxito
- Page Object creado
- Métodos implementados
- Test usando POM funciona