Day 19: Advanced Patterns — Retry, Tags, Parameterization, and Hooks
This is Day 19 of the 21-Day Playwright with TypeScript Challenge. One lesson per day. Zero to production-ready in 3 weeks.
🎭 Want to master this with real projects? Join the Playwright Automation Mastery course at The Testing Academy.
Power features that make your test suite production-grade: retries for CI stability, tags for selective execution, parameterization for data-driven tests, hooks for lifecycle management.
Contents
Retries
// Global retries (CI only)
retries: process.env.CI ? 2 : 0,
// Per-describe retries
test.describe('flaky external service', () => {
test.describe.configure({ retries: 3 });
test('call third-party API', async () => { /* ... */ });
});
Tags for Selective Execution
test('login flow @smoke', async ({ page }) => { /* ... */ });
test('complex workflow @regression', async ({ page }) => { /* ... */ });
test('payment processing @smoke @critical', async ({ page }) => { /* ... */ });
// Run only smoke tests
// npx playwright test --grep @smoke
// Run everything EXCEPT regression
// npx playwright test --grep-invert @regression
🚀 Level Up Your Playwright
From locators to CI pipelines — build a production-grade Playwright + TypeScript framework step by step.
Parameterized Tests
const users = [
{ role: 'admin', canDelete: true },
{ role: 'editor', canDelete: false },
{ role: 'viewer', canDelete: false },
];
for (const user of users) {
test('delete button visibility for ' + user.role, async ({ page }) => {
await loginAs(page, user.role);
await page.goto('/settings');
if (user.canDelete) {
await expect(page.getByRole('button', { name: 'Delete' })).toBeVisible();
} else {
await expect(page.getByRole('button', { name: 'Delete' })).toBeHidden();
}
});
}
Hooks
test.beforeAll(async () => {
// Run once before all tests in file (seed database)
});
test.afterAll(async () => {
// Run once after all tests (cleanup)
});
test.beforeEach(async ({ page }) => {
// Run before each test (navigate to page)
await page.goto('/dashboard');
});
test.afterEach(async ({}, testInfo) => {
// Run after each test (log result)
if (testInfo.status === 'failed') {
console.log('Failed:', testInfo.title);
}
});
test.step() for Sub-Steps
test('complete checkout', async ({ page }) => {
await test.step('add product to cart', async () => {
await page.goto('/products');
await page.getByRole('button', { name: 'Add to cart' }).click();
});
await test.step('fill shipping info', async () => {
await page.goto('/checkout');
await page.getByLabel('Address').fill('123 Main St');
});
await test.step('complete payment', async () => {
await page.getByRole('button', { name: 'Pay' }).click();
await expect(page.getByText('Order confirmed')).toBeVisible();
});
});
Tomorrow (Day 20): AI-assisted Playwright — Codegen, MCP, Claude Code.
🎓 Master Playwright End to End
Join hundreds of SDETs building real automation frameworks. Lifetime access, hands-on projects, and a job-ready portfolio.
