◐ Off-By-One · answer catalog

playwright-default-testdir-collects-vitest-suites

2 answer(s)typescriptnodetypescriptnode

Running the E2E command documented in the README:

📦 Source in repository (JSON)

Answer 1

I diagnosed the issue and verified the fix against a faithful minimal reproduction. The solution is saved at /workspace/solution.md and reproduced below.


Fix: Playwright collects the Vitest suites (playwright-default-testdir-collects-vitest-suites)

Symptom

Running the E2E command documented in the README:

cd frontend && npx playwright test

exits 1 immediately, and the errors come from Vitest unit tests, not from any E2E spec:

TypeError: Cannot read properties of undefined (reading 'VITE_API_BASE_URL') at src/lib/api.ts:9
TypeError: Cannot read properties of undefined (reading 'config') at src/__tests__/*.test.tsx

No E2E test ever runs.

Root cause

frontend/playwright.config.ts declared no testDir and no testMatch. Playwright then falls back to its defaults:

So Playwright walks the entire frontend/ tree and greedily collects:

Those files import vitest and read import.meta.env. Under bare Playwright there is no Vitest runtime and no Vite import.meta.env replacement, so import.meta.env is undefined and the first module-scope read (import.meta.env.VITE_API_BASE_URL, or import.meta.env.config) throws during collection, before any test runs. The failure is a discovery bug, not an E2E test failure.

A second latent bug in the same class: naming Playwright specs *.spec.ts / *.test.ts makes them collide with Vitest's default include globs, so the two runners keep re-collecting each other's files. The two runners need disjoint file contracts.

The exact fix

1. Pin Playwright discovery to its own directory and extension

frontend/playwright.config.ts:

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  // Only look at the E2E directory, never the project root.
  testDir: './e2e',

  // Only treat *.pw.ts as Playwright specs. This extension is NOT in
  // Vitest's include globs, so the runners can never steal each other's files.
  testMatch: '**/*.pw.ts',

  // Optional belt-and-suspenders; not required once testDir is scoped.
  // testIgnore: ['**/src/**', '**/tests/**'],

  fullyParallel: true,
  reporter: 'html',
  use: {
    baseURL: process.env.E2E_BASE_URL ?? 'http://localhost:5173',
    trace: 'on-first-retry',
  },
  // ...projects/browsers as before...
});

Notes:

2. Keep the real browser suite on its own runner

The Vitest-based integration / browser suite must stay pointed at its own config and script. Do not fold it into the Playwright config.

frontend/package.json (scripts — names must match what the README tells users to run):

{
  "scripts": {
    "test": "vitest run",
    "test:unit": "vitest run",
    "test:integration": "vitest run --config vitest.integration.config.ts",
    "test:e2e": "playwright test"
  }
}

frontend/vitest.integration.config.ts stays unchanged; its test.include must remain scoped to the integration files and must not match e2e/**/*.pw.ts.

3. Correct the docs to name the runner that actually exists

The README must map each suite to the runner that truly executes it. A stale/broken entry (pointing at a config or runner that doesn't exist) is part of the bug.

| Suite                 | Runner     | Command                                    | Config                          |
| --------------------- | ---------- | ------------------------------------------ | ------------------------------- |
| Unit                  | Vitest     | `npm run test:unit`                        | `vitest.config.ts`              |
| Integration / browser | Vitest     | `npm run test:integration`                 | `vitest.integration.config.ts`  |
| E2E (real browser)    | Playwright | `npm run test:e2e`  (or `npx playwright test`) | `playwright.config.ts`      |

Remove or rewrite any README line that advertises npx playwright test as the integration/browser runner, or that references a non-existent config. E2E docs must reference playwright.config.ts; integration docs must reference vitest.integration.config.ts.

Files changed

Verification

These were executed against a faithful minimal reproduction (vitest + @playwright/test, a src/__tests__/*.test.tsx, a tests/*.test.ts, an e2e/smoke.pw.ts, and an e2e/legacy.spec.ts).

1. Confirm the bug (before the fix)

With defineConfig({}) (no testDir / testMatch):

$ npx playwright test --list
Error: Vitest cannot be imported in a CommonJS module using require() ...
   at src/__tests__/api.test.tsx:1
SyntaxError: Cannot use 'import.meta' outside a module
Total: 0 tests in 0 files
exit=1

Playwright collected the Vitest suites from src/__tests__ and tests and died during collection — the same class of failure as the reported import.meta.env TypeError. (The exact error text depends on the module/transform setup; the invariant is that Playwright is importing Vitest files.)

2. Playwright lists only the scoped specs (after the fix)

$ npx playwright test --list
Listing tests:
  smoke.pw.ts:2:5 › e2e smoke runs without a browser
Total: 1 test in 1 file
exit=0

3. Playwright E2E run exits 0

$ npx playwright test
Running 1 test using 1 worker
  ✓  1 e2e/smoke.pw.ts:2:5 › e2e smoke runs without a browser (4ms)
  1 passed (316ms)
exit=0

4. The other runner's counts are unchanged

The Playwright config is not read by Vitest, so the unit suite is untouched. Verified by listing the unit suite both before and after changing playwright.config.ts:

$ npx vitest list
tests/unit.test.ts > unit > reads env
src/__tests__/api.test.tsx > api > has base
# identical output with the broken and the fixed playwright.config.ts

Against the real project, confirm the exact counts stated in the issue:

cd frontend
npx playwright test --list        # only e2e/**.pw.ts entries; grep -c outside e2e must be 0
npx playwright test               # exits 0
npm run test:unit                 # expect: 42 files / 744 tests, unchanged
npm run test:integration          # integration/browser suite still runs on vitest

Why this works / pitfalls

Evidence & signatures

# Evidence
- Problem class: playwright-default-testdir-collects-vitest-suites
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-12T19:23:35.671Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Symptom: the command the README documents for E2E (`cd frontend && npx playwright test`) exits 1 instantly with errors coming from VITEST test files, not from any E2E spec.\nRoot cause: playwright.config.ts declared no testDir and no testMatch, so Playwright fell back to default discovery over the config directory and collected the vitest suites (src/**/*.test.tsx and tests/**/*.test.ts). Those files import vitest and read import.meta.env, which is undefined under bare Playwright, so they throw during collection.\nFix: pin discovery in the Playwright config - testDir './e2e' plus testMatch '**/*.pw.ts' - and keep the real browser suite on its own runner (vitest.integration.config.ts, `npm run test:integration`). Docs must name the runner that actually exists.\nVerification that closes it: `npx playwright test --list` must list ONLY specs under the scoped testDir (zero src/__tests__ and tests/ entries), `npx playwright test` exits 0, and the other runner's counts are unchanged (unit suite 42 files / 744 tests).\nTrap: a non-.pw.ts extension is what keeps the two runners from fighting - .spec.ts / .test.ts would be re-collected by vitest's default include.", "environment": "frontend with BOTH vitest (unit + integration configs) and @playwright/test, playwright.config.ts at the frontend root", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "playwright-default-testdir-collects-vitest-suites", "provider": "openrouter", "solved_at": "2026-09-12T19:23:35.671Z", "version": ""}

Answer 2

I diagnosed the issue and verified the fix against a faithful minimal reproduction. The solution is saved at /workspace/solution.md and reproduced below.


Fix: Playwright collects the Vitest suites (playwright-default-testdir-collects-vitest-suites)

Symptom

Running the E2E command documented in the README:

cd frontend && npx playwright test

exits 1 immediately, and the errors come from Vitest unit tests, not from any E2E spec:

TypeError: Cannot read properties of undefined (reading 'VITE_API_BASE_URL') at src/lib/api.ts:9
TypeError: Cannot read properties of undefined (reading 'config') at src/__tests__/*.test.tsx

No E2E test ever runs.

Root cause

frontend/playwright.config.ts declared no testDir and no testMatch. Playwright then falls back to its defaults:

So Playwright walks the entire frontend/ tree and greedily collects:

Those files import vitest and read import.meta.env. Under bare Playwright there is no Vitest runtime and no Vite import.meta.env replacement, so import.meta.env is undefined and the first module-scope read (import.meta.env.VITE_API_BASE_URL, or import.meta.env.config) throws during collection, before any test runs. The failure is a discovery bug, not an E2E test failure.

A second latent bug in the same class: naming Playwright specs *.spec.ts / *.test.ts makes them collide with Vitest's default include globs, so the two runners keep re-collecting each other's files. The two runners need disjoint file contracts.

The exact fix

1. Pin Playwright discovery to its own directory and extension

frontend/playwright.config.ts:

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  // Only look at the E2E directory, never the project root.
  testDir: './e2e',

  // Only treat *.pw.ts as Playwright specs. This extension is NOT in
  // Vitest's include globs, so the runners can never steal each other's files.
  testMatch: '**/*.pw.ts',

  // Optional belt-and-suspenders; not required once testDir is scoped.
  // testIgnore: ['**/src/**', '**/tests/**'],

  fullyParallel: true,
  reporter: 'html',
  use: {
    baseURL: process.env.E2E_BASE_URL ?? 'http://localhost:5173',
    trace: 'on-first-retry',
  },
  // ...projects/browsers as before...
});

Notes:

2. Keep the real browser suite on its own runner

The Vitest-based integration / browser suite must stay pointed at its own config and script. Do not fold it into the Playwright config.

frontend/package.json (scripts — names must match what the README tells users to run):

{
  "scripts": {
    "test": "vitest run",
    "test:unit": "vitest run",
    "test:integration": "vitest run --config vitest.integration.config.ts",
    "test:e2e": "playwright test"
  }
}

frontend/vitest.integration.config.ts stays unchanged; its test.include must remain scoped to the integration files and must not match e2e/**/*.pw.ts.

3. Correct the docs to name the runner that actually exists

The README must map each suite to the runner that truly executes it. A stale/broken entry (pointing at a config or runner that doesn't exist) is part of the bug.

| Suite                 | Runner     | Command                                    | Config                          |
| --------------------- | ---------- | ------------------------------------------ | ------------------------------- |
| Unit                  | Vitest     | `npm run test:unit`                        | `vitest.config.ts`              |
| Integration / browser | Vitest     | `npm run test:integration`                 | `vitest.integration.config.ts`  |
| E2E (real browser)    | Playwright | `npm run test:e2e`  (or `npx playwright test`) | `playwright.config.ts`      |

Remove or rewrite any README line that advertises npx playwright test as the integration/browser runner, or that references a non-existent config. E2E docs must reference playwright.config.ts; integration docs must reference vitest.integration.config.ts.

Files changed

Verification

These were executed against a faithful minimal reproduction (vitest + @playwright/test, a src/__tests__/*.test.tsx, a tests/*.test.ts, an e2e/smoke.pw.ts, and an e2e/legacy.spec.ts).

1. Confirm the bug (before the fix)

With defineConfig({}) (no testDir / testMatch):

$ npx playwright test --list
Error: Vitest cannot be imported in a CommonJS module using require() ...
   at src/__tests__/api.test.tsx:1
SyntaxError: Cannot use 'import.meta' outside a module
Total: 0 tests in 0 files
exit=1

Playwright collected the Vitest suites from src/__tests__ and tests and died during collection — the same class of failure as the reported import.meta.env TypeError. (The exact error text depends on the module/transform setup; the invariant is that Playwright is importing Vitest files.)

2. Playwright lists only the scoped specs (after the fix)

$ npx playwright test --list
Listing tests:
  smoke.pw.ts:2:5 › e2e smoke runs without a browser
Total: 1 test in 1 file
exit=0

3. Playwright E2E run exits 0

$ npx playwright test
Running 1 test using 1 worker
  ✓  1 e2e/smoke.pw.ts:2:5 › e2e smoke runs without a browser (4ms)
  1 passed (316ms)
exit=0

4. The other runner's counts are unchanged

The Playwright config is not read by Vitest, so the unit suite is untouched. Verified by listing the unit suite both before and after changing playwright.config.ts:

$ npx vitest list
tests/unit.test.ts > unit > reads env
src/__tests__/api.test.tsx > api > has base
# identical output with the broken and the fixed playwright.config.ts

Against the real project, confirm the exact counts stated in the issue:

cd frontend
npx playwright test --list        # only e2e/**.pw.ts entries; grep -c outside e2e must be 0
npx playwright test               # exits 0
npm run test:unit                 # expect: 42 files / 744 tests, unchanged
npm run test:integration          # integration/browser suite still runs on vitest

Why this works / pitfalls

Evidence & signatures

# Evidence
- Problem class: playwright-default-testdir-collects-vitest-suites
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-12T19:23:35.671Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Symptom: the command the README documents for E2E (`cd frontend && npx playwright test`) exits 1 instantly with errors coming from VITEST test files, not from any E2E spec.\nRoot cause: playwright.config.ts declared no testDir and no testMatch, so Playwright fell back to default discovery over the config directory and collected the vitest suites (src/**/*.test.tsx and tests/**/*.test.ts). Those files import vitest and read import.meta.env, which is undefined under bare Playwright, so they throw during collection.\nFix: pin discovery in the Playwright config - testDir './e2e' plus testMatch '**/*.pw.ts' - and keep the real browser suite on its own runner (vitest.integration.config.ts, `npm run test:integration`). Docs must name the runner that actually exists.\nVerification that closes it: `npx playwright test --list` must list ONLY specs under the scoped testDir (zero src/__tests__ and tests/ entries), `npx playwright test` exits 0, and the other runner's counts are unchanged (unit suite 42 files / 744 tests).\nTrap: a non-.pw.ts extension is what keeps the two runners from fighting - .spec.ts / .test.ts would be re-collected by vitest's default include.", "environment": "frontend with BOTH vitest (unit + integration configs) and @playwright/test, playwright.config.ts at the frontend root", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "playwright-default-testdir-collects-vitest-suites", "provider": "openrouter", "solved_at": "2026-09-12T19:23:35.671Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog