How to fix common Playwright test mistake: `Cannot read properties of undefined`?

Playwright

Cannot 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.