Escalando Pruebas con Playwright: De Objetos de Página a Inyección de Dependencias y Aislamiento de Datos
En SPAs con renderizado dinámico, los constructores de Objetos de Página crean riesgos debido a la asincronía. Intentar inicializar el estado en el constructor mediante una IIFE conduce a condiciones de carrera: la prueba accede a los datos antes de que estén listos, lo que se manifiesta en CI durante ejecuciones paralelas.
// Incorrecto: estado en el constructor
constructor(page: Page) {
(async () => {
this.initialCount = await page.locator('.items').count();
})();
}
La inicialización síncrona estándar de localizadores también es subóptima: el objeto captura una instantánea del DOM en el momento de la creación. Los getters resuelven el problema con enlace tardío:
// Correcto: getters para el DOM actual
get submitButton() {
return this.page.getByRole('button', { name: 'Place Order' });
}
Esto asegura un comportamiento sin estado: cada llamada ve el DOM actual, sin estar ligada al estado inicial.
Inyección de Dependencias mediante Fixtures de Playwright
La creación directa de objetos en las pruebas (new LoginPage(page)) complica la refactorización: cambiar el constructor requiere ediciones en todo el proyecto. Los fixtures actúan como un contenedor de DI con almacenamiento en caché de instancias.
export const test = base.extend({
cartPage: async ({ page }, use) => {
await use(new CartPage(page));
},
checkoutFlow: async ({ cartPage, checkoutPage }, use) => {
await use(new CheckoutFlow(cartPage, checkoutPage));
}
});
test('place order', async ({ authFlow, checkoutFlow }) => {
await authFlow.loginAs(user);
await checkoutFlow.submitOrder();
});
Las pruebas se centran en la especificación, independientemente de la implementación.
Escalando Fixtures: mergeTests y Espacios de Nombres
Con 20+ fixtures, un archivo monolítico se vuelve inmanejable. Divide por dominio con mergeTests:
// auth.fixtures.ts
export const authTest = base.extend({ ... });
// fixtures.ts
import { mergeTests } from '@playwright/test';
export const test = mergeTests(authTest, cartTest);
Evita conflictos de nombres mediante espacios de nombres:
export const test = base.extend({
auth: async ({ page }, use) => {
await use({ admin: new Admin(page), user: new User(page) });
}
});
test('Admin can log in', async ({ auth }) => {
await auth.admin.loginAs();
});
Aislamiento de Datos en Ejecuciones Paralelas
workerIndex es insuficiente para CI con fragmentos. Usa una combinación:
- RUN_ID de CI (ej., GITHUB_RUN_ID)
- testId—hash de la ruta de la prueba
- repeatEachIndex para reintentos
Inicializar Faker con esta clave compuesta asegura reproducibilidad:
export function seedFaker(testInfo: TestInfo) {
const RUN_ID = process.env.RUN_ID || 'local';
const seed = hashCode(`${testInfo.testId}-${RUN_ID}`);
faker.seed(seed);
return faker;
}
export const test = base.extend({
faker: async ({}, use, testInfo) => {
await use(seedFaker(testInfo));
}
});
Las ejecuciones locales con CI-RUN_ID reproducen datos exactos de fallos.
Capas de Datos: Fábricas, Sobrescrituras, Conjuntos de Datos
- Faker: ruido para campos no críticos (nombres, email)
- Fábricas: estructura con valores por defecto
- Sobrescrituras: cambios críticos en pruebas
- Conjuntos de Datos: casos de negocio recurrentes
export function createUser(overrides?: Partial<User>, f = faker): User {
return {
id: f.string.uuid(),
name: f.person.fullName(),
role: 'customer',
...overrides
};
}
export const VIP_USER = { role: 'vip', discount: 0.15 };
Prueba: const vipUser = createUser({ ...VIP_USER }, faker);
Semántica de Pasos y Guardias ESLint
Separa clics técnicos de lógica de negocio con @Step:
@Step('Realizando pedido #${orderId}')
async submitOrder(orderId: string) {
await this.checkoutPage.fillDetails(orderId);
await this.checkoutPage.submit();
}
Los pasos de negocio ('Iniciar sesión') son más resistentes a cambios de UI que los técnicos ('Hacer clic en el botón de inicio de sesión').
ESLint prohíbe new PageObject directo:
{
"no-restricted-syntax": ["error", {
"selector": "NewExpression[callee.name=/.*Page$/]",
"message": "Usa fixtures en lugar de new"
}]
}
Conclusiones Clave
- Los getters en POM aseguran el DOM actual sin trampas de estado
- Los fixtures como DI minimizan la refactorización con 1000+ pruebas
- RUN_ID + testId inicializa Faker para estabilidad en fragmentos de CI
- Fábricas + conjuntos de datos aíslan datos sin código redundante
- Los espacios de nombres en fixtures previenen colisiones en mergeTests
— Editorial Team
Aún no hay comentarios.