DEV Community

Styrow.dev
Styrow.dev

Posted on Originally published at styrow.dev

Designing a resilient test harness for concurrent, stateful E2E scenarios in Playwright

🔥 Playwright Hard Mode: Conquering Concurrent Stateful E2E Tests!
Scenario: Building a large-scale Playwright E2E suite where hundreds of tests run concurrently, each needing a unique, isolated, and complex backend state (e.g., specific user orders, inventory levels). How do you architect for true isolation, rapid state provisioning, and robust cleanup?

📌 Problem Statement
Running concurrent, stateful E2E tests presents significant challenges for reliable CI/CD feedback. Cross-test contamination due to shared backend resources leads to flaky, hard-to-debug failures.
❌ Relying solely on Playwright's browser context isolation is insufficient for backend state.
✅ We need a production-grade strategy for managing external state, ensuring each test worker operates on a clean, dedicated environment.

💡 Solution & Code Walkthrough

Isolation Strategy: Ephemeral Environments per Worker
Leverage dynamic, containerized environments for true isolation.
Docker Compose or Testcontainers can spin up a fresh, dedicated microservice stack (including databases) for each Playwright worker process or even per test file.
• Use globalSetup and globalTeardown in playwright.config.ts to orchestrate container lifecycle. globalSetup starts the environments, globalTeardown cleans them up.
• Each environment gets unique port mappings and database instances, preventing any cross-talk.

State Provisioning: API-First, Idempotent Fixtures
Use custom Playwright fixtures to provision specific states before UI interaction via direct API or database calls. This is significantly faster than UI-driven setup.
• Ensure provisioning logic is idempotent: running it multiple times yields the same state without errors.

// playwright/fixtures/statefulUser.ts
import { test as base } from '@playwright/test';
import axios from 'axios'; // Example for API interaction

type MyFixtures = {
  statefulUser: { userId: string; token: string; orderId: string };
};

export const test = base.extend<MyFixtures>({
  statefulUser: [async ({}, use) => {
    // 1. Create unique user via API
    const userRes = await axios.post('http://localhost:8080/api/users', { name: 'Test User' });
    const userId = userRes.data.id;
    const token = userRes.data.token;

    // 2. Provision complex state (e.g., 3 orders) via API
    await axios.post(`http://localhost:8080/api/users/${userId}/orders`, { items: ['itemA'] });
    await axios.post(`http://localhost:8080/api/users/${userId}/orders`, { items: ['itemB'] });
    const orderRes = await axios.post(`http://localhost:8080/api/users/${userId}/orders`, { items: ['itemC'] });
    const orderId = orderRes.data.id;

    // Provide the stateful data to the test
    await use({ userId, token, orderId });

    // Optional: Teardown specific user state if not handled by ephemeral env cleanup
    // await axios.delete(`http://localhost:8080/api/users/${userId}`);
  }, { scope: 'test' }], // 'test' scope for per-test fixture
});

// Example test usage:
// import { test } from '../fixtures/statefulUser';
// test('should display user orders', async ({ page, statefulUser }) => {
//   await page.goto(`/dashboard?user=${statefulUser.userId}`);
//   // Assert on page content using statefulUser.orderId etc.
// });
Enter fullscreen mode Exit fullscreen mode

Resource Cleanup: Robust Teardown
• For ephemeral environments, globalTeardown is critical to gracefully stop and remove containers.
• Within tests, test.afterEach can handle specific cleanup (e.g., logging out, deleting temporary files).
• Crucially, ensure cleanup hooks execute even if a test fails to prevent resource leaks that impact subsequent runs. Playwright's afterEach and globalTeardown handle this by default.

🔑 Key Takeaways
Isolation First: Use container orchestration (Docker Compose, Testcontainers) for truly isolated, ephemeral backend environments per worker.
API-Driven State: Provision complex test prerequisites via direct API/DB calls using custom Playwright fixtures for speed and idempotency.
Robust Cleanup: Implement globalTeardown for environment destruction and afterEach for test-specific cleanup, ensuring execution even on failure.

Quick Summary Q&A
Q: How to ensure isolation? A: Ephemeral containerized environments (Docker) per worker, orchestrated by Playwright's globalSetup/globalTeardown.
Q: How to provision state fast? A: Custom Playwright fixtures making direct, idempotent API/DB calls before UI interaction.
Q: How to clean up reliably? A: globalTeardown for environments, afterEach for test artifacts, designed to run even upon test failure.

TAGS: playwright, e2e testing, test automation, ci/cd, microservices, stateful tests, test isolation, docker, testcontainers
────────────────────────────────────────
🚀 Elevate your testing skills! Download our app for more advanced Playwright scenarios and expert guides.
────────────────────────────────────────

📲 𝐅𝐑𝐄𝐄 𝐌𝐎𝐁𝐈𝐋𝐄 𝐀𝐏𝐏 — 𝟔𝟎𝟎+ 𝐒𝐃𝐄𝐓 𝐐&𝐀𝐬
Practice real-world interview scenarios offline on the free QA Automation & SDET Prep app:

🤖 𝐆𝐨𝐨𝐠𝐥𝐞 𝐏𝐥𝐚𝐲 (𝐀𝐧𝐝𝐫𝐨𝐢𝐝):
https://play.google.com/store/apps/details?id=com.app.seleniuminterviewquestions&referrer=utm_source%3Ddevto%26utm_medium%3Darticle%26utm_campaign%3Dselenium_20260905

🍎 𝐀𝐩𝐩 𝐒𝐭𝐨𝐫𝐞 (𝐢𝐎𝐒):
https://apps.apple.com/app/id6786760948?pt=128640464&ct=devto_selenium_20260905&mt=8

────────────────────────────────────────
────────────────────────────────────────

Top comments (0)