Škálování testů v Playwright: od Page Objects k DI a izolaci dat
V SPA s dynamickým vykreslováním představují konstruktory Page Objects riziko kvůli asynchronní povaze. Pokus o inicializaci stavu v konstruktoru pomocí IIFE vede k podmínkám závodu: test přistupuje k datům dříve, než jsou připravena, což se projeví v CI při paralelním spuštění.
// Špatně: stav v konstruktoru
constructor(page: Page) {
(async () => {
this.initialCount = await page.locator('.items').count();
})();
}
Standardní synchronní inicializace lokátorů je také suboptimální – objekt zachytí snímek DOM v okamžiku vytvoření. Gettery problém řeší pozdním vázáním:
// Správně: gettery pro aktuální DOM
get submitButton() {
return this.page.getByRole('button', { name: 'Dokončit objednávku' });
}
Tím je zajištěno bezstavové chování: každé volání vidí aktuální DOM, bez vazby na počáteční stav.
Dependency Injection pomocí fixture v Playwright
Přímé vytváření objektů v testech (new LoginPage(page)) komplikuje refaktorování: změna konstruktoru vyžaduje úpravy v celém projektu. Fixture fungují jako DI kontejner s ukládáním instancí do mezipaměti.
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('dokončení objednávky', async ({ authFlow, checkoutFlow }) => {
await authFlow.loginAs(user);
await checkoutFlow.submitOrder();
});
Testy se soustředí na specifikaci, nezávisle na implementaci.
Škálování fixture: mergeTests a Namespacing
Při 20+ fixture se monolitický soubor stává nezvladatelným. Rozdělte je podle domén pomocí mergeTests:
// auth.fixtures.ts
export const authTest = base.extend({ ... });
// fixtures.ts
import { mergeTests } from '@playwright/test';
export const test = mergeTests(authTest, cartTest);
Vyhnete se konfliktům názvů pomocí namespacing:
export const test = base.extend({
auth: async ({ page }, use) => {
await use({ admin: new Admin(page), user: new User(page) });
}
});
test('Administrátor se může přihlásit', async ({ auth }) => {
await auth.admin.loginAs();
});
Izolace dat při paralelním spouštění
workerIndex nestačí pro CI s rozdělením testů. Použijte kombinaci:
- RUN_ID z CI (GITHUB_RUN_ID)
- testId – hash cesty testu
- repeatEachIndex pro opakované pokusy
Semínko Fakeru tímto složeným klíčem zajišťuje reprodukovatelnost:
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));
}
});
Lokální spuštění s CI-RUN_ID reprodukuje přesná data selhání.
Vrstvy dat: továrny, přepsání, datové sady
- Faker: šum pro nedůležitá pole (jména, email)
- Továrny: struktura s výchozími hodnotami
- Overrides: kritické změny v testu
- Datasets: opakující se obchodní případy
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 };
Test: const vipUser = createUser({ ...VIP_USER }, faker);
Sémantika kroků a ESLint strážci
Oddělte technické kliknutí a obchodní logiku pomocí @Step:
@Step('Dokončení objednávky #${orderId}')
async submitOrder(orderId: string) {
await this.checkoutPage.fillDetails(orderId);
await this.checkoutPage.submit();
}
Obchodní krok ('Autorizace') je odolnější vůči změnám UI než technický ('Kliknutí na Přihlásit se').
ESLint zakazuje přímé new PageObject:
{
"no-restricted-syntax": ["error", {
"selector": "NewExpression[callee.name=/.*Page$/]",
"message": "Použijte fixture místo new"
}]
}
Co je důležité
- Gettery v POM zajišťují aktuální DOM bez stavových pastí
- Fixture jako DI minimalizují refaktorování při 1000+ testech
- RUN_ID + testId nastavuje semínko Fakeru pro stabilitu v CI rozdělení
- Továrny + datové sady izolují data bez nadbytečného kódu
- Namespacing fixture zabraňuje kolizím v mergeTests
— Editorial Team
Zatím žádné komentáře.