DEV Community

Silviu Technology
Silviu Technology

Posted on

Playwright Email Tests: Catch the Wrong Message

One of the sneakiest email-test bugs is not "message never arrived." It is "the test opened the wrong one and still looked half-correct." That happens a lot in Playwright suites that run in parallel, retry failed specs, or reuse inboxes for convenience. Teams often search for terms like temp mail generator, tempmailso, tepm mail com, and temp gamil com while debugging, but the real problem is usually message identity rather than message delivery.

The fix is to treat email verification like a QA traceability problem. Do not only ask, "Did an email show up?" Ask, "Can I prove this exact email belongs to this exact run?" Once you frame it that way, the flaky parts get smaller, and the workflow gets much more repeatable.

Why Playwright often opens the wrong email

There are four common causes.

First, a shared inbox still contains yesterday's verification link. Second, your app sends more than one mail for the same user action. Third, retries create a second account while the first email is still sitting there. Fourth, the test matches on subject alone, which is way too weak for transactional mail.

This is why a post about email change confirmation checks is useful beyond React itself. The core lesson is that link assertions need stronger context than "latest message with a familiar subject." The same applies to preview environment inbox isolation, where inbox collisions create failures that look random but are actually very systematic.

In practice, wrong-message bugs are annoying because they do not always fail loudly. Sometimes the test clicks an older link, lands on a valid page, and only breaks three steps later. That makes the root cause look unrelated, which is why the investigation can drift for hours if the logs are thin.

A safer pattern: create, trigger, poll, then verify

For Playwright, I prefer a four-step flow:

  1. Create a unique inbox for this test run.
  2. Trigger the app behavior that sends the email.
  3. Poll for a message using narrow filters.
  4. Verify the email content includes a run-specific clue before opening any link.

Here is a compact example:

test("verifies the signup email safely", async ({ page }) => {
  const inbox = await mailHelper.createInbox();
  const runToken = `signup-${Date.now()}`;

  await page.goto("/signup");
  await page.getByLabel("Email").fill(inbox.address);
  await page.getByLabel("Password").fill("Sup3rSafePass!");
  await page.getByLabel("Referral code").fill(runToken);
  await page.getByRole("button", { name: "Create account" }).click();

  const message = await mailHelper.waitForMessage({
    inboxId: inbox.id,
    subjectIncludes: "Verify your account",
    timeoutMs: 45000,
  });

  expect(message.html).toContain(runToken);
  const verifyUrl = extractLink(message.html, "verify");
  await page.goto(verifyUrl);
  await expect(page.getByText("Email verified")).toBeVisible();
});
Enter fullscreen mode Exit fullscreen mode

The important part is not the exact helper API. It is the sequencing. A unique inbox removes old noise. A run token proves the message belongs to this scenario. Narrow polling reduces false positives. And only then do you extract and open the link.

If your team uses a burner email flow for test isolation, the same rule still applies: unique address first, verification second. If you prefer tempmailso or another disposable inbox service, do not let the tool choice distract from the assertion design. Weak matching stays weak no matter which mailbox provider you pick.

How to prove the message belongs to this run

This is the part many suites skip, and it is where most reliability gains are hiding.

Add one trace value that you can safely assert inside the email body or metadata. It can be a referral code, state token suffix, environment label, or test-run marker. It does not need to expose secrets, and it should not change the product behavior. It just needs to let the test say, "yes, this message was generated for me."

When a test fails, log these values before rerunning:

  • Inbox ID and recipient address
  • Test run token or correlation marker
  • Subject line and received timestamp
  • Number of matching messages found
  • Whether the chosen link was tied to the current run

That tiny checklist saves a suprising amount of time. Instead of vague "email step flaky" notes, you get evidence. Maybe the app generated two messages. Maybe the worker picked the old one. Maybe the message body never included the expected marker because the async job used stale data. Those are fixable bugs, but they are hard to separate if the test only asserts on the subject and moves on.

A quick reliability checklist

  • Use one inbox per test, not one inbox per suite
  • Match on scenario plus a run-specific clue
  • Keep polling in one helper instead of scattered waits
  • Log message metadata when assertions fail
  • Open links only after the message identity is confirmed
  • Review retries carefully, because they can mask inbox reuse

This workflow is not fancy, but it is dependable. Once the suite proves message identity before consuming the link, Playwright failures become less mysterious and more actionable. That is a better place for QA to be, even if the first version feels a bit more strict.

Q&A

Should I always add a run token?

If the email flow is important and the test runs in CI, yes, probably. The extra signal is cheap and it prevents alot of ambiguous failures.

What if I cannot change the app payload?

Then assert on the strongest available metadata: recipient, send time window, environment marker, and message purpose. It is not perfect, but it is better than grabbing the latest email and hoping.

Are retries enough to stabilize email tests?

No. Retries can hide the symptom for a bit, but they will not fix inbox reuse or weak message selection. In some suites they make the diagnosis worse, becuase they add more messages to inspect.

Top comments (0)