QA How-To
How to Fix "Playwright Test did not expect test() to be called here"
Fix Playwright Test did not expect test to be called here by tracing config imports, wrong runners, duplicate packages, and CI or Docker resolution. quickly.
19 min read | 2,915 words
TL;DR
Run `npx playwright test --list` from the test package. If registration fails, stop direct Node or unit-runner execution, remove config imports that reach specs, or resolve duplicate @playwright/test copies; then rerun collection and one selected test.
Key Takeaways
- Use npx playwright test --list to isolate collection from browser execution.
- Run spec files through Playwright Test, not node or a unit test runner.
- Keep test() calls out of configuration files and their imported modules.
- Trace transitive imports from configuration to spec files before editing a valid test.
- Compare resolved @playwright/test instances in workspaces and fixture packages.
- Verify dependency resolution and collection inside the failing CI job or container.
If you need to fix Playwright Test did not expect test to be called here, the failure usually appears while Playwright loads files, before any browser opens. It means test() ran outside the suite collection context that the Playwright Test runner creates. The quickest clue is which process loaded the file and whether the stack trace points into a config import, a spec launched directly with Node, or a second copy of the test package.
Error: Playwright Test did not expect test() to be called here.
Most common reasons include:
- You are calling test() in a configuration file.
- You are calling test() in a file that is imported by the configuration file.
- You have two different versions of @playwright/test. This usually happens
when one of the dependencies in your package.json depends on @playwright/test.
TL;DR
Run npx playwright test --list from the package that owns your tests. If it fails, check whether a config file imports a spec, whether you ran the spec with node or another test runner, and whether npm ls @playwright/test playwright --all exposes multiple installations. Keep test() calls in discovered spec or setup test files, use the Playwright CLI to collect them, and align the runner package resolved by the CLI and the spec. After changing code, run npx playwright test --list again, followed by one selected spec.
What the Error Actually Means
A Playwright spec registers tests when its module is evaluated. The Playwright Test runner first loads configuration, selects projects and files, then evaluates each selected test file inside a collector. A call to test() during configuration loading or under an unrelated process has no active suite to receive it. Playwright stops at registration and prints this error. It has not reached a page fixture, an assertion, or a browser launch.
The line highlighted in the stack trace is often the innocent test('name', ...) line. Follow the import chain upward instead of changing that call. For example, playwright.config.ts -> ./e2e/index.ts -> ./e2e/login.spec.ts tells you that configuration imported a module that evaluated a spec too early. A bare node tests/login.spec.js command tells you that Node, not the test runner, evaluated it. A stack containing two distinct node_modules locations suggests two test package instances.
npx playwright test --list is a useful boundary check: it loads configuration and collects tests without launching a browser. A successful list proves collection, while a later failure during execution needs a different investigation. Playwright documents this flag in its command line reference. If your new error is No tests found, inspect testDir, testMatch, and the working directory instead of returning to this diagnostic; the Playwright runner guide covers discovery.
Root-Cause Decision Table
| Symptom | Root cause | Fix |
|---|---|---|
node tests/example.spec.js throws immediately |
A spec was run without Playwright Test | Run npx playwright test tests/example.spec.js |
playwright.config.ts itself calls test() |
Test registration occurs during configuration evaluation | Move the case to a spec, or use a setup project |
Config imports a helper that imports *.spec.ts |
A transitive import evaluates tests too early | Split data and helpers from spec files; remove the import edge |
CLI and spec resolve different @playwright/test paths |
Duplicate package instance, often a workspace or shared fixture package | Deduplicate and make the fixture package use the consuming runner |
| Failure appears only under Jest, Vitest, or a custom Node script | Another runner evaluates a Playwright spec | Route browser specs through Playwright Test only |
| Local CLI passes, CI or Docker fails during collection | Different working directory or dependency tree | Reproduce installation and resolution in that environment |
| CLI passes, VS Code test action fails | Editor command or extension chooses another runner/context | Compare command and workspace, then run the CLI from the test package |
1. Fix Playwright Test Did Not Expect Test to Be Called Here When You Run a Spec With Node
A Playwright spec is a module for the Playwright Test CLI to collect. Node can load its JavaScript, but it cannot create the suite context by itself. The official Playwright issue showing this exact diagnostic used node test/test.js to launch a file containing a test() call. The import succeeded; registration failed. Switching to the runner changes the execution context, not the test body.
Create playwright.config.ts at the package root and tests/example.spec.ts under it:
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
});
// tests/example.spec.ts
import { test, expect } from '@playwright/test';
test('arithmetic sanity check', async () => {
expect(2 + 2).toBe(4);
});
Run the file through Playwright Test:
npx playwright test --list
npx playwright test tests/example.spec.ts
The first command should list arithmetic sanity check; the second should report one passing test. This example uses no browser fixture, so it isolates runner registration from browser installation. Avoid node tests/example.spec.ts, tsx tests/example.spec.ts, or a package script that executes the spec directly. Define an npm script such as "test:e2e": "playwright test" if teammates need a short command. If the CLI itself cannot be found, solve package installation first with the missing Playwright module guide.
2. Fix Playwright Test Did Not Expect Test to Be Called Here in the Config File
The config is evaluated before Playwright selects a spec. It may import defineConfig, devices, environment parsing functions, and plain data. It cannot register a test as a side effect. A tempting mistake is to place a quick environment probe or login check directly under playwright.config.ts because it must run before the suite. That location is precisely why registration fails.
Keep the config declarative and put the check in a discovered file:
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
testMatch: '**/*.spec.ts',
});
// tests/environment.spec.ts
import { test, expect } from '@playwright/test';
test('required base URL is configured', async () => {
const baseURL = process.env.BASE_URL;
expect(baseURL, 'BASE_URL must be set').toBeTruthy();
});
BASE_URL=https://example.com npx playwright test --list
BASE_URL=https://example.com npx playwright test tests/environment.spec.ts
The list should contain the environment test; its run should pass when BASE_URL is set. If this check must happen before other browser tests, use a setup project with dependencies rather than importing a setup spec into the config. Playwright's global setup documentation explains that a dependency project runs its setup test under the normal runner and appears in reports. The Playwright global setup guide walks through that arrangement. A globalSetup function is a different API: it exports a function that receives the resolved config. Do not call test() from that function or import a spec into it.
For a minimal setup project, name a dedicated file and limit the setup project's match to that file:
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
projects: [
{ name: 'setup', testMatch: 'setup.spec.ts' },
{ name: 'chromium', testIgnore: 'setup.spec.ts', dependencies: ['setup'] },
],
});
// tests/setup.spec.ts
import { test, expect } from '@playwright/test';
test('validate test environment', async () => {
expect(process.env.BASE_URL).toBeTruthy();
});
BASE_URL=https://example.com npx playwright test --list
BASE_URL=https://example.com npx playwright test --project=setup
Use this alternate config in place of the preceding one, not alongside it. The selected setup project should list and run one test. Add an actual Chromium spec before running the dependent project. testIgnore prevents the setup test from being repeated in that project; dependencies ensures setup precedes its browser tests.
3. Remove a Config Import That Reaches a Spec Indirectly
A config can look clean and still load a test through a barrel file. Suppose the config imports ./tests, the directory index re-exports login.spec.ts, and that spec registers tests at module load. The stack trace may point at login.spec.ts even though nobody typed its path in the config. This is common when test data and test definitions share an index.ts barrel.
Replace the barrel dependency with a small data-only module:
// test-support/settings.ts
export const testSettings = {
baseURL: 'https://example.com',
} as const;
// playwright.config.ts
import { defineConfig } from '@playwright/test';
import { testSettings } from './test-support/settings';
export default defineConfig({
testDir: './tests',
use: { baseURL: testSettings.baseURL },
});
// tests/home.spec.ts
import { test, expect } from '@playwright/test';
test('home page has a heading', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('heading')).toBeVisible();
});
rg 'spec\.ts|tests/' playwright.config.ts test-support tests
npx playwright test --list
The search helps expose a surviving import edge; the list should collect the home test without throwing. A full browser run requires browsers installed and a reachable page, so the list is the immediate verification for this particular fix. Keep page objects as classes or functions with no test() calls at import time. Keep data generators independent of specs. A helper can be imported by a spec; a config should import only helpers that do not transitively import specs. If you use custom fixtures, the fixture override examples show how to make fixture modules explicit without bundling test cases into them.
4. Resolve Two Copies of @playwright/test
The error text names two different versions of @playwright/test because a spec can register through one package instance while the CLI's collector belongs to another. This can happen when a shared fixture package installs its own runner dependency or a workspace resolves a nested copy. Identical looking imports are insufficient proof that both modules resolve to the same file. Check the dependency graph from the package where the command fails.
npm ls @playwright/test playwright --all
node -p "require.resolve('@playwright/test')"
npx playwright --version
Look for multiple physical locations or version branches in npm ls. In a workspace, run it again from each package that imports test. If your shared package publishes fixtures, declare @playwright/test as a peer dependency there and install it as a development dependency for that package's own checks. The consuming test package should declare its runner directly. This lets the consumer provide the single runner used by both fixtures and specs.
A shared package manifest can express that relationship without assuming a version here:
{
"name": "@acme/e2e-fixtures",
"peerDependencies": {
"@playwright/test": "<match-your-installed-compatible-range>"
},
"devDependencies": {
"@playwright/test": "<match-your-installed-version>"
}
}
Those angle-bracket strings are placeholders to replace with the actual version or compatible range in your lockfile; do not paste them as published semver. In the consuming package, use its own @playwright/test development dependency, reinstall through the package manager that owns the lockfile, and recheck the graph. For npm workspaces, npm dedupe may flatten compatible ranges, but verify the resulting tree and collection rather than assuming flattening solved the mismatch.
npm dedupe
npm ls @playwright/test playwright --all
npx playwright test --list
The tree should resolve to a compatible single runner instance for the CLI and imported fixtures, and the test list should appear. If the CLI passes while an editor tool fails with custom fixtures, compare how that tool resolves the fixture package; a known Playwright VS Code issue describes a separate loading context. Avoid a blind global package upgrade as the first response: the decisive evidence is module identity and the lockfile, not the newest version number.
5. Keep Jest, Vitest, and Standalone Scripts Away From Playwright Specs
Jest and Vitest discover files using their own patterns. If one of them picks up tests/login.spec.ts, it will import the file without Playwright's collector. The thrown message can look like a Playwright problem even though the wrong process loaded the file. Similarly, a custom migration script that imports a test helper through a barrel may accidentally import a spec. Separating the file sets makes the runner boundary visible.
For Vitest, a compact configuration can restrict unit test discovery while Playwright owns browser specs:
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: ['src/**/*.test.ts'],
exclude: ['tests/**'],
},
});
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
testMatch: '**/*.spec.ts',
});
// tests/registration.spec.ts
import { test, expect } from '@playwright/test';
test('registered only by Playwright', async () => {
expect('playwright').toContain('wright');
});
// src/math.test.ts
import { test, expect } from 'vitest';
test('registered only by Vitest', () => {
expect(2 + 2).toBe(4);
});
npx vitest list
npx playwright test --list
npx playwright test tests/registration.spec.ts
Vitest should list math.test.ts only; Playwright should list registration.spec.ts, whose selected run should pass. For Jest, set testMatch or testPathIgnorePatterns to keep the tests/ browser directory out of unit discovery. Do not import a Playwright spec in a Jest setup file. Shared pure functions can live in src/ or test-support/ and be imported by both suites. A Playwright fixture module should be imported only by Playwright specs or other Playwright fixture modules.
6. Reproduce the CI or Docker Environment That Fails
When a laptop passes and a clean job fails, compare the exact working directory, lockfile, and command. npx may use a different local package from the one your spec resolves, especially in a workspace. A CI job that installs production dependencies only can also lose the intended test package. Run the checks inside the failing job, before changing assertions or browser settings.
A GitHub Actions job can install from the committed lockfile and verify collection before browsers start:
name: Playwright collection and tests
on: [push, pull_request]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: lts/*
- run: npm ci --include=dev
- run: npm ls @playwright/test playwright --all
- run: npx playwright test --list
- run: npx playwright install --with-deps
- run: npx playwright test
Run the same verification locally from the package root:
npm ci --include=dev
npm ls @playwright/test playwright --all
npx playwright test --list
The Playwright CI guide discusses the full pipeline after test discovery works. Playwright's official CI instructions use npm ci, browser installation, and the CLI in that order.
The official Docker image supplies browsers and system dependencies; it does not install your project's @playwright/test package. Build from the same lockfile and choose an image tag that exactly matches your installed Playwright release. Replace the placeholder below with the version printed by npx playwright --version before building:
FROM mcr.microsoft.com/playwright:v<your-playwright-version>-noble
WORKDIR /work
COPY package.json package-lock.json ./
RUN npm ci --include=dev
COPY . .
RUN npx playwright test --list
CMD ["npx", "playwright", "test"]
npx playwright --version
docker build -t playwright-collection-check .
docker run --rm --init --ipc=host playwright-collection-check
A successful build proves collection inside the image; the container run then verifies browser execution if your tests can reach their application. Ensure .dockerignore excludes host node_modules so the copy step does not replace container-installed modules. For more image details, read the Docker for Playwright guide and official Docker documentation.
7. Diagnose an Editor-Only Failure Without Rewriting Passing Tests
An IDE can launch a test from another folder, use another selected config, or invoke an extension with its own discovery behavior. Begin with a control run in the integrated terminal of the exact workspace folder containing playwright.config.ts. Record the command and the package path instead of assuming the highlighted line is defective.
pwd
node -p "require.resolve('@playwright/test')"
npx playwright test --list
npx playwright test tests/example.spec.ts
If the same spec passes by CLI, inspect the editor's selected workspace and test configuration. In VS Code, open the folder that owns the config, choose the intended Playwright project in the Test Explorer, and retry. If a custom fixtures package is involved, compare its resolved test package against the terminal result from section 4. Playwright's VS Code guide documents the supported editor flow. Verify the final editor action by seeing the same test title and outcome as the CLI. Use the CLI in CI even when the editor remains a separate local workflow.
How to Verify the Fix
Verify collection before exercising the whole application. Start in the package where the test dependency is declared and inspect the resolved executable and test names:
node -p "require.resolve('@playwright/test')"
npm ls @playwright/test playwright --all
npx playwright test --list
npx playwright test tests/example.spec.ts --workers=1
Replace tests/example.spec.ts with one existing spec. A clean --list run means configuration and selected test modules loaded under the collector. A passing selected run adds execution evidence. --workers=1 makes the first diagnostic run easier to read; it is not a permanent cure. If the next failure is a missing browser executable, install the browsers for the installed package and follow the browser executable fix. If it is a page or locator timeout, follow the stack trace for that new failure rather than changing the registration repair.
Check the exit status in CI, not just log text. Do not use --pass-with-no-tests while proving this fix; zero collected tests should fail visibly. When multiple projects exist, npx playwright test --list --project=chromium can confirm the project you actually ship, provided the project is named chromium. The Playwright command line reference defines both flags.
Prevent It From Coming Back
Keep a clear import direction: config may import plain settings; specs may import fixtures and helpers; helpers must not import specs. Name browser tests *.spec.ts under a dedicated tests/ directory and unit tests *.test.ts under their unit runner's directory. This makes accidental cross-discovery easier to notice in review. Avoid a barrel that re-exports both data and specs.
Keep the runner dependency explicit in the package that runs browser tests. A shared fixture package should not bundle an incompatible private runner instance. In CI, install from the lockfile, run npm ls @playwright/test playwright --all, then run npx playwright test --list before browser installation or application startup. That sequence catches import and dependency mistakes cheaply. Do not pin a Docker image to a different Playwright release from the lockfile; use the installed version for the tag.
When adding a setup step, choose a setup project if it needs fixtures, tracing, retries, or report visibility. Keep globalSetup as a function when you intentionally use that API. Neither approach requires importing a spec into the config. Review the global setup examples before refactoring setup code.
Interview Questions and Answers
Q: What phase emits this error? During module evaluation and test registration. Playwright has not started the browser or executed the callback passed to test(). That timing is why browser flags and locator changes cannot repair it.
Q: Why can a correct test() line still fail? The module may have been imported by playwright.config.ts, by a config helper, or by another test runner. The call is valid only when the Playwright Test collector is evaluating a selected test file. Trace the loader rather than rewriting the assertion.
Q: What does --list establish? It checks that the chosen config and test files can be loaded and collected without launching browsers. It does not prove app reachability or fixture behavior during execution. Follow it with a selected test run.
Q: How do duplicate packages cause the message? The CLI and test file may hold different runner instances, each with separate collection state. A nested dependency or workspace package can create that split. Compare resolved paths and the package tree before changing dependency constraints.
Q: Should globalSetup contain test()? No. A globalSetup module exports a function for initialization. A setup project is a Playwright test project, so its selected setup spec can register a normal test().
Q: Why does a laptop pass while Docker fails? The container performs a clean install and may resolve another package tree or run from another directory. The official image supplies browsers but not the project test package. Check the lockfile install and collection inside the image.
For runner interview preparation, see Playwright Test runner interview questions and practice explaining the import chain rather than memorizing the error text.
Common Mistakes
- Replacing
test()with a different assertion or adding waits. The error occurs before test execution, so neither change affects collection. - Installing browsers first. Browser binaries matter only after the test package and runner have loaded the suite.
- Running
npm lsonly at the repository root in a workspace. The failing package may resolve a nested copy. - Importing
tests/index.tsinto the config when that barrel exports spec files. Split configuration data into a module with no test registration. - Using
--pass-with-no-teststo make a CI gate green. That can hide a brokentestDirafter the original error disappears. - Updating every dependency without checking the lockfile and resolved paths. This can mask the import problem or introduce an unrelated compatibility failure.
- Treating an editor-only failure as proof that the CLI test is invalid. Compare the two execution contexts before editing fixtures.
Conclusion
To fix Playwright Test did not expect test to be called here, identify who evaluated the spec, then move registration into Playwright's discovered test context or unify the runner instance. Run npx playwright test --list after each repair and one selected spec before the full suite. That pair of checks separates a registration fix from later browser or application failures.
Interview Questions and Answers
At what point does Playwright throw this registration error?
It occurs while modules are loaded and `test()` tries to register a case. The callback body has not run, and no browser fixture has started. I therefore inspect the loader and import graph before looking at browser behavior.
How would you prove a config import is the cause?
I follow the stack from the failing spec through its importer chain to `playwright.config.ts`. I remove the config import that reaches the spec and place shared data in a side-effect-free module. `npx playwright test --list` then proves collection succeeds.
Why is running a spec with node different from using Playwright CLI?
Node evaluates JavaScript but does not create Playwright Test collection state. The Playwright CLI loads config, selects projects and files, and evaluates specs under its collector. I run the file with `npx playwright test path/to/spec.ts`.
How would you diagnose two runner instances?
I inspect `npm ls @playwright/test playwright --all` and compare resolved package paths from the CLI package and any shared fixture package. A nested copy can register tests against a collector different from the one running the CLI. I align dependency declarations and verify the installed tree before re-running collection.
When should setup use a project dependency?
When setup needs Playwright fixtures, report visibility, traces, or retries, I use a dedicated setup test project. Its spec can call `test()` because the runner collects it normally. A `globalSetup` function is a different hook and must not register tests.
What does `npx playwright test --list` prove and what does it not prove?
It proves the selected configuration and spec modules can be loaded and tests can be collected. It does not launch the browser or validate application behavior. I follow it with a selected test run and then the full suite.
How can a unit test runner cause this Playwright error?
Jest or Vitest may import a browser spec based on a broad discovery glob. That evaluates `test()` outside Playwright Test collection. I separate the discovery patterns and keep browser specs under Playwright-owned paths.
Frequently Asked Questions
Why does Playwright say it did not expect test() here?
A test file registered a case while Playwright had no active collector for it. Common triggers are direct Node execution, a config import reaching the spec, and separate test package instances. Use the failing command and stack trace to identify which context loaded the module.
Can I run a Playwright spec with node?
No. Node can evaluate the module but does not establish Playwright Test suite collection. Use `npx playwright test path/to/file.spec.ts` and confirm its discovery with `npx playwright test --list`.
Can playwright.config.ts import a test file?
It should not. Configuration is evaluated before specs are collected, so importing a spec can trigger `test()` too early. Move shared settings to a data-only module and let the runner discover the spec under `testDir`.
Does installing Playwright browsers fix this error?
Usually not. This failure occurs during test registration, before a browser launch. Install browser binaries only if a later run reports a missing executable or system dependency.
How do I check for duplicate @playwright/test copies?
Run `npm ls @playwright/test playwright --all` in the failing package and compare `require.resolve` results from packages that import the runner. Inspect shared fixture package dependencies and the lockfile before deduplicating or applying overrides.
Why does the error happen only in CI?
A clean CI install can expose a different dependency tree, omitted dev dependencies, or a different working directory. Install from the lockfile in the test package and run `npx playwright test --list` in the same job before browser execution.
What if the Playwright CLI passes but VS Code fails?
Compare the editor workspace, selected project, and test package resolution with the passing CLI invocation. An extension can load custom fixtures through a different context. Keep the passing spec intact while investigating that integration.
Related Guides
- How to Fix Playwright "Element is not attached to the DOM"
- How to Fix Playwright expect received value must be a Locator
- How to Fix Playwright waiting for element to be visible enabled and stable
- How to Add CI to a test framework (2026)
- How to Add logging to a test framework (2026)
- How to Add reporting to a test framework (2026)