|

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.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.