DEV Community

Silviu Technology
Silviu Technology

Posted on

Parallel Playwright Email Tests Without Cross-Talk

Parallel Playwright runs are great until email checks start borrowing evidence from each other. I do not see most failures coming from the click itself. They come from weak mailbox ownership, broad polling, or assertions that can not prove which worker created which message. In QA, that is where the real fix usualy lives.

Teams sometimes say a disposable address setup or a free disposable email tool is the risky part. In practice, the flake usually starts earlier. One worker reuses an alias pattern, another worker polls the same inbox too broadly, and suddenly the test suite is green for the wrong reason. I have even seen scratch notes like tempail left in helpers, which is a small sign the inbox side evolved faster than the test contract.

Patterns like CI inbox isolation and parallel email test isolation are useful because they push teams toward ownership boundaries instead of bigger and bigger waits.

Why parallel workers expose weak inbox ownership

With one worker, a vague inbox assertion may look fine for weeks. Run the same suite with four workers in CI and the weak points show up very fast.

The first issue is alias collision. If two tests produce similar recipient names, an inbox search by subject or time window can pick the wrong message. The second issue is stale mail. A worker finds a leftover verification email from a prior run, the assertion passes, and nobody notices the current flow never sent anything. The third issue is duplicate delivery. One test clicks twice, or a retry fires after a network wobble, and the suite now has more than one plausible message.

That is why I prefer to treat mailbox checks as evidence collection. A Playwright assertion should answer three questions: which worker sent the email, which run it belongs to, and why this exact message is the right one. If it can not do that, the pass signal is a bit too soft.

The worker-scoped contract I recommend

My default contract is simple on purpose:

  1. Create one alias per worker and per test run.
  2. Include a runId in the app request or test-only metadata.
  3. Poll using recipient, runId, and a narrow created-after timestamp.
  4. Fail if more than one matching message appears.
  5. Log enough context so another tester can replay the lookup.

This sounds strict, but it makes triage much faster. If worker 3 fails while workers 1, 2, and 4 pass, I do not want a vague "email not found" message. I want the alias, the runId, and the query window right there in the report.

For Playwright and QA work, I have found that this boring contract scales better than clever heuristics. Clever matching can seem fine locally, then it gets weird in CI when the worker pool is busy or the queue is a little slow.

A Playwright example for worker-safe email assertions

Here is a compact pattern that keeps the mailbox query tied to one worker:

import { test, expect } from "@playwright/test";

test("verification email belongs to this worker run", async ({ page }, testInfo) => {
  const workerId = testInfo.workerIndex;
  const runId = `signup-${workerId}-${Date.now().toString(36)}`;
  const email = `qa+${runId}@example.test`;
  const createdAfter = new Date().toISOString();

  await page.goto("/signup");
  await page.getByLabel("Email").fill(email);
  await page.getByLabel("Password").fill("SuperSecret123!");
  await page.getByRole("button", { name: "Create account" }).click();

  await expect(page.getByText("Check your inbox")).toBeVisible();

  const message = await inbox.waitForMessage({
    to: email,
    runId,
    createdAfter,
    timeoutMs: 30000
  });

  expect(message.subject).toContain("Verify your email");
  expect(message.html).toContain(runId);
  expect(message.to).toBe(email);
});
Enter fullscreen mode Exit fullscreen mode

The detail I care about most is not the helper name. It is the query shape. Recipient plus runId plus time boundary is much safer than subject text alone. When that check fails, the failure is more legible and less annoyng to debug.

What to log when one worker fails and others pass

When only one parallel worker goes red, I capture a short set of facts first:

  • worker index
  • generated alias
  • runId passed through the flow
  • polling start time and timeout
  • number of candidate messages returned before filtering

Those five values make the next step obvious more often than not. Zero candidates usually means delivery lag or a broken trigger. Several candidates mean the lookup is too broad. One candidate with the wrong runId means the mailbox is returning stale evidence.

If your system lets you inspect queue or webhook latency, add that too. A 2024 Google Cloud reliability report notes that correlation IDs are one of the most useful debugging handles in distributed systems because they make cross-service timelines easier to reconstruct (source). That principle maps very cleanly to email testing in CI.

A short QA checklist

Before I call a parallel email test reliable, I want these boxes checked:

  • each worker creates its own alias
  • the mailbox query filters by recipient, runId, and recent time
  • duplicate matches fail on purpose
  • logs show enough detail to replay the lookup
  • the test report makes worker-level failures easy to spot

If those are already true, your Playwright suite is probly closer to stable than it feels on a noisy morning.

Q&A

Is one inbox per worker enough?

Not by itself. I still want one alias per run inside that worker, otherwise retries and reruns can smear evidence together.

Should I just increase the timeout for slow email delivery?

Sometimes yes, but only after the ownership contract is solid. A longer wait can hide the bug for a bit, it does not explain it.

Do I need this level of rigor for every flow?

No. I use it first on signup, password reset, and other high-value checks where a false pass is expensive. That tends to give the best return for QA teams.

Top comments (0)