I once watched two engineers spend a whole afternoon on a failing build. They read the app code, checked the database, and blamed the server. The real cause was two tests that both created a customer named "Test User".
Let's walk through why this is so hard to see.
You have two tests. Both create a contact named "Dana Fox".
Run the first alone: it passes. Run the second alone: it passes. Run them together: the second fails, because it expected one Dana Fox and found two.
Now it gets worse. Playwright runs test files in parallel by default. So the failure moves around depending on timing. Someone re-runs the build and it passes. Nobody can reproduce it on their machine.
The fix is to give each test its own data:
test('creates a contact', async ({ page }) => {
const name = `Dana Fox ${Date.now()}`;
await page.getByTestId('contact-name-input').fill(name);
await page.getByTestId('save-contact').click();
await expect(page.getByTestId('contact-row').filter({ hasText: name })).toBeVisible();
});
Date.now() gives the current time in milliseconds, so every run gets a different name. Two tests cannot collide, and neither can two runs of the same test.
About cleaning up. Deleting the data your test created is good practice. But never write a test that only passes because an earlier test cleaned up properly. Runs crash. Machines get stopped. A test that needs a perfectly clean database will fail one day for no clear reason.
A tip that will save you hours. Creating data by clicking through forms is slow and fragile. Use the API instead, through Playwright's request object:
test.beforeEach(async ({ request }) => {
await request.post('/api/contacts', {
data: { name: 'Maya Chen', email: 'maya@lumina.io' },
});
});
This takes milliseconds instead of seconds, and a change to the contact form cannot break your setup.
Remember this: every test creates its own data with a value no other test uses. Your test must pass alone, first, last, or beside others.
Reference: Parallelism.