Skip to content

DIAGNOSTICS AND DELIVERY · LESSON 22 OF 24

Parallelism, retries and flaky tests

Scale execution without sharing mutable test data.

  • workers
  • fullyParallel
  • retries
  • repeat-each

Your runnable checkpoint

Extract into a new practice folder. Each download contains tests, configuration, the complete local practice app and an offline lesson. Use Node.js 22 or 24. Each checkpoint is independent.

Install dependencies and browser binaries using the commands below. The test runner starts the practice server on port 4174. Tests target localhost, never the production EduPoly site.

1. Understand the concepts

Workers are separate processes used to run tests. fullyParallel allows tests within files to run in parallel. Every scenario must own its browser state and any server-side data it changes.

A retry repeats a failed test; it does not repair the cause. A test that fails first and passes on retry is flaky evidence. Investigate unstable selectors, missing waits, shared records and environmental dependencies.

The course uses zero retries locally and one in CI to preserve evidence while allowing transient failures to be diagnosed. Do not use test.only in committed suites; forbidOnly catches it in CI.

Use --repeat-each=3 with multiple workers to expose hidden order dependencies. The lab’s browser-local task storage is context-isolated, while API examples require unique namespaces. Do not solve collisions by globally resetting everyone’s data.

2. Build and run

These are complete test files and configuration. The download also includes app/ and server.mjs; preserve their paths. Stop any manually started practice server before running the suite.

Terminal
npm install
npx playwright install chromium
npm run check
npm test

tests/lesson.spec.ts

tests/lesson.spec.ts
import { test, expect } from '@playwright/test';
for (const name of ['Worker A', 'Worker B', 'Worker C']) {
  test(`isolated task for ${name}`, async ({ page }) => {
    await page.goto('/');
    await expect(page.getByTestId('task-row')).toHaveCount(0);
    await page.getByLabel('Task title', { exact: true }).fill(name);
    await page.getByRole('button', { name: 'Add task', exact: true }).click();
    await expect(page.getByTestId('task-row')).toHaveCount(1);
    await expect(page.getByTestId('task-row')).toContainText(name);
  });
}

playwright.config.ts

playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 1 : 0,
  workers: process.env.CI ? 2 : undefined,
  timeout: 30000,
  expect: { timeout: 5000 },
  reporter: [['list'], ['html', { open: 'never' }]],
  use: { baseURL: 'http://127.0.0.1:4174', trace: 'retain-on-failure', screenshot: 'only-on-failure' },
  projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
  webServer: { command: 'node server.mjs', url: 'http://127.0.0.1:4174', reuseExistingServer: false, timeout: 15000 }
});

package.json

package.json
{
  "name": "edupoly-playwright-parallelism-retries-and-flakes",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "test": "playwright test",
    "test:headed": "playwright test --headed",
    "test:ui": "playwright test --ui",
    "check": "tsc --noEmit",
    "report": "playwright show-report",
    "serve": "node server.mjs"
  },
  "devDependencies": {
    "@playwright/test": "1.56.1",
    "typescript": "5.9.3",
    "@types/node": "22.20.5"
  }
}

tsconfig.json

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "CommonJS",
    "moduleResolution": "Node",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "types": [
      "node"
    ],
    "noEmit": true
  },
  "include": [
    "tests/**/*.ts",
    "pages/**/*.ts",
    "playwright.config.ts"
  ]
}

.gitignore

.gitignore
node_modules/
playwright-report/
test-results/
playwright/.auth/
.env

3. Trace the code and check the result

  1. Each generated test has a unique name.
  2. The empty-state assertion detects accidental shared storage.
  3. The final count checks that another worker did not inject extra data.

Verify your result

  • Three tests pass with multiple workers.
  • A repeated run does not accumulate tasks across tests.

4. Practice independently

Run this checkpoint three times per test with two workers.

Show the explanation

Use npm test -- --workers=2 --repeat-each=3. Expect nine passing executions and investigate any flaky retry instead of ignoring it.

When something goes wrong

If a browser executable is missing, run npx playwright install chromium in the project folder. If port 4174 is occupied, stop your manual practice server. Read assertion call logs for mismatched locators or expectations. Use npm run report to inspect failures; do not add fixed sleeps or broad resets to hide them.

Read the related official Playwright documentation ↗