The Playwright test runner

Learn how to give your test suite some structure.

So far, we've only looked at the internals of a test run. But how can you control when and how your tests are run?

test / test.describe

When your test files grow, you can group tests with test.describe.

import { test, expect } from "@playwright/test";test.describe("playwright", () => {  test("has title", async ({ page }) => {    // ...  });  test("get started link", async ({ page, browserName }) => {    // ...  });});

Tags

Tag tests or whole describe blocks to organize them and filter at runtime. Tags are passed via the options object and conventionally start with @.

// tag a whole describe blocktest.describe("checkout flow", { tag: "@smoke" }, () => {  test("can add to cart", async ({ page }) => {    // ...  });});// tag individual tests with one or multiple tagstest("checkout works", { tag: ["@smoke", "@critical"] }, async ({ page }) => {  // ...});

Filter tests by tag from the CLI with --grep:

$ npx playwright test --grep @smoke

You can also dedicate a project to a tag in your playwright.config.ts. This is very useful when tagged tests should run with different settings (retries, timeouts, browsers) or as a separate CI job.

import { defineConfig } from "@playwright/test";export default defineConfig({  projects: [    {      name: "smoke",      grep: /@smoke/,      use: {        /* ... */      },    },  ],});

Then run that project on its own:

npx playwright test --project=smoke

beforeAll, beforeEach, afterEach, afterAll

Playwright provides common test runner methods you might already be familiar with.

import { test, expect } from "@playwright/test";test.describe("playwright", () => {  test.beforeAll(async () => {    console.log("Before tests");  });  test.beforeEach(async ({ page }) => {    console.log("Before each");  });  test("has title", async ({ page }) => {    // ...  });  test("get started link", async ({ page, browserName }) => {    // ...  });  test.afterEach(async ({ page }) => {    console.log("After each");  });  test.afterAll(async () => {    console.log("After tests");  });});

The page object is isolated per test, but it's shared with the lifecycle hooks running around that test, such as beforeEach and afterEach.

For example, you can log in on a website in a beforeEach hook, and every test will then start with its own, already logged-in page.

Todo

Individual test configuration

There are multiple ways to configure how individual tests run.

test.only

If you're focusing on a single test during development, you can tell the test runner to run only this test.

test.only("focus this test", async ({ page }) => {  // Run only focused tests in the entire project.});

test.only is handy in debugging sessions.

test.fixme

Don't run tests but mark them as fixme to look at them later.

// skip an entire test and mark it as `fixme`test.fixme("test to be fixed", async ({ page }) => {  // ...});// skip test depending on a condition and mark it as `fixme`test("broken in WebKit", async ({ page, browserName }) => {  test.fixme(    browserName === "webkit",    "This feature is not implemented on Mac yet",  );  // ...});
Todo

test.slow

Mark a test as slow and triple the auto-waiting timeouts.

test("has title", async ({ page, browserName }) => {  test.slow(browserName === "webkit", "This feature is slow on Mac");  // ...});

test.slow() will also add a custom annotation to your Playwright test report.

slow annotation

test.skip

Skip a test.

// skip test entirelytest.skip("broken test", async ({ page }) => {  // ...});// skip test when it's run in webkittest("skip in WebKit", async ({ page, browserName }) => {  test.skip(    browserName === "webkit",    "This feature is not implemented for Mac",  );  // ...});

Test steps

For longer and more complex tests, it can help to add a third level of grouping: groups, tests and test steps.

test.describe("workshop store", () => {  test("add the first product to the cart", async ({ page }) => {    await test.step("Open the first product", async () => {      await page.goto("/");      await page        .getByTestId("hero-product-grid")        .getByRole("link")        .first()        .click();    });    await test.step("Add to cart", async () => {      await page.getByLabel("Add item to cart").click();    });  });});

Test steps are a nice way to make your test reports more readable.

test steps

Todos

Test information

In addition to the handy test methods, you can access and enrich the collected test information using the testInfo object.

Custom annotations

fixme or slow tests are annotated with their labels in the report.

fixme annotation

It's also possible to add your own annotations to the test report.

test("is logged in", async ({ loggedInPage }, testInfo) => {  testInfo.annotations.push({    type: "Some thing is a 🐟y here",    description: "https://some-url.com",  });  // ...});

Custom annotations are useful if you want to reference or link to other material.

A custom annotation

Todo

Hands on

Group and tag your tests

Exercise 1 of 2

Tag search and cart and run them on mobile Chrome

  1. In tests/shop.spec.ts, wrap your cart-related tests in a test.describe("cart", { tag: "@cart" }, ...) block.
  2. Set up a new Playwright cart project that greps for its tag.
  3. Increase the overall timeout for cart tests.
  4. Run the project on its own: npx playwright test --project=cart. Confirm only the matching tests execute.
Exercise 2 of 2

Think of more projects

How would you structure and group a growing Playwright test suite?

  • Would you implement smoke tests?
  • Would you group by functionality and area?
  • Would you have a critical suite?

Think about your ideal approach and set up new projects.