How to fix common Playwright test mistake: `Cannot read properties of undefined`?
PlaywrightCannot read properties of undefined in a Playwright test is a JavaScript runtime error, not a browser or network error. It surfaces when test code tries to access a property or method on a value that is undefined — typically because a fixture argument was not destructured correctly in the test function signature, a helper function returned undefined, or the result of a Playwright async operation wasn't awaited before being used. The error appears at the Node.js level, not in the browser.
Common mistake
// Missing fixture destructuring — `page` is undefined in callback
test('navigates to profile', async () => {
await page.goto('https://app.example.com/profile');
// ^^^^ TypeError: Cannot read properties of undefined (reading 'goto')
});
Another common form when the helper's return value is used without awaiting:
async function getUser(page) {
const response = await page.evaluate(() => fetch('/api/user').then(r => r.json()));
return response;
}
test('shows user name', async ({ page }) => {
const user = getUser(page); // Missing await — user is a Promise, not the value
await expect(page.getByText(user.name)).toBeVisible();
// ^^^^ user.name is undefined on a Promise object
});
The fix
Always destructure Playwright fixtures from the test callback, and await every async helper:
import { test, expect } from '@playwright/test';
// Correct fixture destructuring
test('navigates to profile', async ({ page }) => {
await page.goto('https://app.example.com/profile');
await expect(page.getByRole('heading', { name: 'Profile' })).toBeVisible();
});
For helper functions, always await them:
async function fetchUserData(page: Page): Promise<{ name: string; email: string }> {
return page.evaluate(() =>
fetch('/api/user').then((r) => r.json())
);
}
test('shows user name', async ({ page }) => {
await page.goto('https://app.example.com');
const user = await fetchUserData(page); // Properly awaited
await expect(page.getByRole('heading', { name: user.name })).toBeVisible();
});
For custom fixtures that may return undefined when setup fails:
import { test as base, expect } from '@playwright/test';
type Fixtures = { loggedInPage: Page };
const test = base.extend<Fixtures>({
loggedInPage: async ({ page }, use) => {
await page.goto('/login');
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('secret');
await page.getByRole('button', { name: 'Sign in' }).click();
await page.waitForURL('**/dashboard');
await use(page); // Always call use() or the fixture hangs
},
});
test('dashboard loads', async ({ loggedInPage }) => {
await expect(loggedInPage.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});
Why it works
Playwright's test() function passes fixture values through the object destructured from the async callback parameter. If the parameter is async () => without destructuring, no fixtures are injected — page, context, and request will all be undefined in that scope. TypeScript with strict mode catches this at compile time, which is a strong argument for using TypeScript in Playwright test suites. Awaiting async helpers ensures the resolved value is used rather than the raw Promise object.
Tips
- Enable TypeScript in your Playwright project — it will catch missing awaits and wrong fixture signatures at compile time before tests run.
- Use ESLint with the @typescript-eslint/no-floating-promises rule to catch unawaited async calls statically.
- When a custom fixture produces undefined, check that use(value) is always called exactly once inside the fixture implementation — forgetting it causes the fixture to yield undefined to the test.
- Related structural error: if you see test() called here unexpectedly, see did not expect test() to be called here.