Writing Your First Playwright Test#

In this guide, we’ll walk through creating a basic Playwright test from scratch. You’ll learn how to:

  • Structure a test file

  • Navigate to pages

  • Interact with elements

  • Make assertions

  • Handle test fixtures

Basic Test Structure#

A Playwright test follows this basic pattern:

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

test("test name", async ({ page }) => {
  // Test code goes here
});

Example: Testing a Login Form#

Let’s create a complete test that logs into a website:

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

test("successful login", async ({ page }) => {
  // Navigate to the login page
  await page.goto("https://demo.playwright.dev/login");

  // Fill in the login form
  await page.getByLabel("Username").fill("user");
  await page.getByLabel("Password").fill("password123");

  // Click the login button
  await page.getByRole("button", { name: "Login" }).click();

  // Assert successful login - check for welcome message
  await expect(page.getByText("Welcome, user!")).toBeVisible();

  // Assert URL has changed to dashboard
  await expect(page).toHaveURL(/.*dashboard/);
});

Testing Multiple Scenarios#

You can add multiple test cases in a single file:

test("failed login with incorrect password", async ({ page }) => {
  await page.goto("https://demo.playwright.dev/login");

  await page.getByLabel("Username").fill("user");
  await page.getByLabel("Password").fill("wrong-password");

  await page.getByRole("button", { name: "Login" }).click();

  // Assert error message is shown
  await expect(page.getByText("Invalid credentials")).toBeVisible();

  // Assert we're still on the login page
  await expect(page).toHaveURL(/.*login/);
});

Test Hooks#

You can use test hooks to set up and clean up test resources:

test.beforeEach(async ({ page }) => {
  // This runs before each test in this file
  await page.goto("https://demo.playwright.dev/login");
});

test.afterEach(async ({ page }) => {
  // This runs after each test in this file
  await page.close();
});

Grouping Tests#

Organize related tests using test descriptions:

test.describe("Authentication", () => {
  test("login with valid credentials", async ({ page }) => {
    // Test code...
  });

  test("login with invalid credentials", async ({ page }) => {
    // Test code...
  });
});

Running a Specific Test#

You can run a single test by adding .only:

test.only("focus on this test", async ({ page }) => {
  // Only this test will run
});

Or skip tests with .skip:

test.skip("skip this test", async ({ page }) => {
  // This test will be skipped
});

Next Steps#

Once you’re comfortable with basic tests, move on to:

  1. Using page objects to organize test code

  2. Creating custom fixtures for test setup

  3. Managing test data

  4. Running tests in parallel

Basic Test Example#

Simple Test Structure#

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

test("test name", async ({ page }) => {
  // Test code goes here
});

Form Interaction Example#

test("login test", async ({ page }) => {
  await page.goto("/login");

  // Fill form fields
  await page.fill('input[name="username"]', "testuser");
  await page.fill('input[name="password"]', "password123");

  // Submit form
  await page.click('button[type="submit"]');

  // Assert successful login
  await expect(page.locator(".welcome-message")).toBeVisible();
});

Element Assertions#

test("element assertions", async ({ page }) => {
  await page.goto("/products");

  // Check if element is visible
  await expect(page.locator(".product-item")).toBeVisible();

  // Check element count
  await expect(page.locator(".product-item")).toHaveCount(5);

  // Check text content
  await expect(page.locator("h1")).toHaveText("Products");
});

Tip

Use locators over selectors when possible for better readability and maintainability.