Getting started

Write your first test

The shape of a test here, and the two files it lives in.

Cue set up a working example when you ran init. Let’s walk through it, because everything else follows the same shape.

Two files, two jobs

A test here is split across two files, and the split is the whole idea.

The test says what a person does. Read it out loud and it should sound like a sentence.

tests/app/functional/login.spec.ts
test('a known user reaches their dashboard', { tag: ['@smoke'] }, async ({ loginPage }) => {
  await loginPage.goto();
  await loginPage.logIn('user@example.com', 'correct-password');

  await expect(loginPage.welcomeHeading).toBeVisible();
});

The page object says where things are. All the fiddly details live here.

pages/app/login.page.ts
export class LoginPage {
  get emailField() {
    return this.page.getByLabel('Email');
  }

  get logInButton() {
    return this.page.getByRole('button', { name: 'Log in' });
  }

  get welcomeHeading() {
    return this.page.getByRole('heading', { name: /welcome/i });
  }
}

Why bother splitting them

When a designer renames a button, you change one line in one file. Without the split, you’d search the whole test suite for every place that button appears.

That’s it. That’s the entire reason.

Run it

npx playwright test

Or just the fast ones:

npx playwright test --grep @smoke

Three habits that matter

Wait for things, not for seconds

// Don't
await page.waitForTimeout(5000);

// Do — this waits exactly as long as it needs to
await expect(loginPage.welcomeHeading).toBeVisible();

Tag every test

test('user can log in', { tag: ['@smoke'] }, async ({ loginPage }) => {

Tags decide when a test runs. @smoke means “fast and reliable enough to run on every push”. Without a tag, a test either runs everywhere or nowhere — and both are wrong.

The full list of tags →

Let the fixture hand you the page

// Do — it arrives ready to use
test('...', async ({ loginPage }) => {

// Don't — now this test knows how to build one
const loginPage = new LoginPage(page);

If a check stops you

You’ll see a message explaining what’s wrong, why, and what to write instead. Want the longer version?

npx @wiluszdamian/cue explain no-hard-waits

Next