How to fix Playwright Test error: "did not expect test() to be called here"?
PlaywrightThis error means a test() call was executed outside of a spec file — most commonly because a helper module or config file was imported somewhere that Playwright's test runner does not expect to find test registrations. The test runner sets up a collection phase where test(), test.describe(), and test.beforeEach() are valid, and a separate execution phase. When code in a non-spec context (a shared utility, a config file, or a custom reporter) calls test() directly, the runner throws this error.
Common mistake
// helpers/auth-helper.ts — this is a shared utility, not a spec file
import { test } from '@playwright/test';
// BAD: test() registration inside a helper module
test('user can login', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('user@example.com');
});
Also triggered by importing a spec file from playwright.config.ts or from another spec file's top level (outside of test.describe or test.beforeAll).
The fix
Move all test(), test.describe(), and test.beforeEach() calls into *.spec.ts (or *.test.ts) files only. Extract reusable logic into plain async functions:
// helpers/auth.ts — plain helper, no test() calls
import type { Page } from '@playwright/test';
export async function login(page: Page, email: string, password: string) {
await page.goto('/login');
await page.getByLabel('Email').fill(email);
await page.getByLabel('Password').fill(password);
await page.getByRole('button', { name: 'Sign in' }).click();
await page.waitForURL('**/dashboard');
}
// tests/dashboard.spec.ts — test registrations live here
import { test, expect } from '@playwright/test';
import { login } from '../helpers/auth';
test('dashboard shows user name', async ({ page }) => {
await login(page, 'user@example.com', 'secret');
await expect(page.getByRole('heading', { name: 'Ada' })).toBeVisible();
});
For shared setup across tests, use fixtures instead of test() calls in shared files:
// fixtures/authenticated.ts
import { test as base } from '@playwright/test';
import { login } from '../helpers/auth';
export const test = base.extend({
authenticatedPage: async ({ page }, use) => {
await login(page, process.env.TEST_USER!, process.env.TEST_PASS!);
await use(page);
},
});
Why it works
Playwright's test runner uses a separate process for collection (finding and registering tests) and for execution. The test() function works by registering a callback in the collection phase's global state. When test() is called in a module that the runner loads for a different purpose (config evaluation, reporter loading, utility import), there is no active collection context and the runner throws. Keeping test() calls strictly inside spec files guarantees they are only ever invoked during the collection phase.
Tips
- If your project structure makes it hard to tell which files are specs, add a testMatch pattern to playwright.config.ts to be explicit: testMatch: ['**/*.spec.ts'].
- Watch for barrel index files (index.ts) that re-export both test helpers and test registrations — the re-export causes test calls to run in any context that imports from that barrel.
- This error also appears when a spec file accidentally imports another spec file — Playwright will try to register those tests twice in unexpected collection contexts.
- For JavaScript projects without TypeScript, ESLint's no-restricted-imports rule can flag imports of test from @playwright/test in non-spec files as a safeguard.