Skip to main content
Ashish KumarSDET II
All projects

Multi-Layer Playwright Framework

One order verified at every layer: UI, REST/GraphQL, Postgres, Kafka, and WebSocket.

Playwright · TypeScript · Docker · PostgreSQL · Kafka · GraphQL · GitHub Actions

Results

154
tests across UI, API, DB, Kafka, WebSocket, and E2E
34
@smoke tests on every pull request
12
parallel test jobs in every nightly full run

Problem

In an event-driven app, the expensive failures live between layers: an order that's saved but never announced, stock that goes negative under concurrent orders, events out of order, or a real-time update reaching the wrong user. UI tests alone can't see most of that.

Approach

  • Built a shop app to test (Express, Postgres, Kafka, WebSocket, GraphQL) in Docker Compose, and one Playwright + TypeScript framework with page objects, typed REST/GraphQL/WebSocket/Kafka clients, and worker-scoped DB and Kafka fixtures.
  • Put each risk at the layer that checks it directly: OpenAPI contract validation and security cases at the API, persistence and rollback checks in Postgres, schema, keys, and ordering on Kafka, per-user pushes over WebSocket, and a thin end-to-end layer proving the layers agree.
  • Tiered CI by risk: a @smoke set on every pull request, the full suite nightly, sharded API and UI jobs, and cross-browser and mobile runs limited to smoke.
  • Opt-in chaos tests stop Kafka mid-flow; one exposed a real defect (the order is saved but the API returns 500), kept as an expected failure that documents it.

From the code

tests/e2e/order-flow.spec.ts
@e2e @smoke
/**
 * UI -> REST / GraphQL -> Database -> Kafka / WebSocket -> UI
 * One business transaction verified at every layer.
 */
// …
await test.step('DB: order row, item row and stock', async () => {
  // …
  const stock = await db.queryOne<{ stock: number }>(
    'SELECT stock FROM products WHERE id = $1', [product.id]);
  expect(stock!.stock).toBe(23);
});

await test.step('Kafka: order.created event', async () => {
  const event = await listener.waitForEvent(
    (e) => e.value?.order?.id === orderId && e.value.eventType === 'order.created',
  );
  expect(validate(OrderEventSchema, event.value).order.total).toBe(39.98);
});

Outcome

A public framework that tests a real event-driven system layer by layer, with CI tiers that match cost to risk and a documented defect the chaos tests caught.

Business Impact

The way I'd structure quality for any multi-service product: test each risk where it's cheapest to catch, and keep end-to-end tests for proving the layers agree.