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:
- Using page objects to organize test code 
- Creating custom fixtures for test setup 
- Managing test data 
- 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.
