Skip to content

Playwright Basics

Before writing real tests, you need a mental model of Playwright's core pieces: the test runner, fixtures, async actions, and how a typical project is organized. This lesson walks through those building blocks with runnable examples.

The Page Fixture and Async Actions

Every Playwright test receives fixtures — most commonly page, a single browser tab. All navigation and interaction methods (goto, click, fill) are asynchronous and must be awaited.

Locators represent elements on the page. They are lazy: creating a locator does not query the DOM immediately. Playwright re-resolves locators at action and assertion time, which powers auto-waiting.

import { test, expect } from '@playwright/test';

test('dashboard menu', async ({ page }) => {
  await page.goto('/dashboard');
  await page.getByRole('button', { name: 'Account' }).click();
  await expect(page.getByRole('menuitem', { name: 'Logout' })).toBeVisible();
});

Each await waits for the action or assertion to succeed (or time out). No manual sleep is needed for typical UI updates.

Default Playwright Project Structure

playwright.config.ts
tests/
  example.spec.ts
  auth.setup.ts
package.json
  • playwright.config.ts configures browsers, base URL, timeouts, and reporters.
  • tests/ holds spec files named *.spec.ts by convention.
  • @playwright/test provides test, expect, and built-in fixtures.
  • Run tests with npx playwright test from the project root.

Playwright Basics Cheat Sheet

Essential vocabulary before writing your first spec file.

Term Meaning
Fixture Built-in dependency injected into tests (page, context, request)
Page A single browser tab for navigation and interaction
Locator A query for element(s), resolved at action time
Spec file A file containing tests (*.spec.ts)
Auto-wait Playwright waits for elements to be actionable before acting
Config playwright.config.ts centralizes project settings
Headless Run browsers without a visible window (default in CI)

Why Every Playwright Call Needs await

Playwright's API is promise-based. Forgetting await means the test may finish before actions complete, producing false passes or confusing errors.

// Wrong: click may not finish before the assertion runs
page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByText('Saved')).toBeVisible();

// Right
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByText('Saved')).toBeVisible();

Enable @typescript-eslint/no-floating-promises to catch missing awaits in TypeScript projects.

Anatomy of a Spec File

import { test, expect } from '@playwright/test';

test.describe('Login page', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/login');
  });

  test('shows error for invalid credentials', async ({ page }) => {
    await page.getByLabel('Email').fill('bad@example.com');
    await page.getByLabel('Password').fill('wrong');
    await page.getByRole('button', { name: 'Sign in' }).click();
    await expect(page.getByRole('alert')).toContainText('Invalid credentials');
  });
});

test.beforeEach() runs before every test in the describe block.

Common Mistakes

  • Forgetting await on Playwright methods, causing race conditions.
  • Storing locators in variables and expecting them to stay valid across navigations without re-querying.
  • Putting all tests in one giant spec file instead of organizing by feature.
  • Confusing page (one tab) with context (isolated session) or browser (process).

Key Takeaways

  • Playwright tests are async; always await actions and assertions.
  • The page fixture is your primary interface for browser interaction.
  • Locators are lazy and re-resolved, enabling reliable auto-waiting.
  • Project structure centers on playwright.config.ts and tests/*.spec.ts.

Pro Tip

Install the official Playwright VS Code extension — it highlights missing awaits, runs tests inline, and opens Trace Viewer on failure.