Playwright: A Fast and Practical Introduction

Published 07-09-2024

Article Preview
// ✅ Writing & Running Tests
test("A basic test", async ({ page }) => {
  // ✅ How to Navigate
  await page.goto("https://playwright.dev/");
  page.getByRole("link", { name: "Get started" }).click();

  // ✅ How to Select Elements & Perform Actions
  await page
    .getByRole("listitem")
    .filter({ hasText: "Product A" })
    .getByRole("button", { name: "Add to cart" })
    .click();

  // ✅ How to Write Assertions / Expects
  await page.getByRole("button", { name: "submit" }).toBeEnabled();

  // ✅ How to Mock API calls with test data
  await page.route("*/**/api/v1/fruits", async (route) => {
    const json = [{ name: "Strawberry", id: 21 }];
    await route.fulfill({ json });
  });
});

This guide will cover 5 fundamental concepts about Playwright. These concepts should help guide you to start writing useful and effective tests, while following best practices.

Installation instructions: If you haven’t already installed Playwright, see Playwright Documentation - Installing Playwright.

There are two main ways to navigate in Playwright.

  • URL: page.goto(url) navigates Playwright directly to a URL.
  • Actions: for example crawling the page by clicking links.

Below we create an example in our tests folder called example.spec.ts.

tests/example.spec.ts
import { test } from "@playwright/test";

test("A basic test", async ({ page }) => {
  // Load page directly via URL
  await page.goto("https://playwright.dev/");

  // Navigate to the page via links
  page.getByRole("link", { name: "Get started" }).click();
});

⚡ Navigating, actions, selectors, etc… are performed through the Playwright page object provided by the Playwright test.

To run our Playwright tests:

  • Headless mode: npx playwright test
  • UI mode: npx playwright test --ui

Selecting

Selecting elements is achieved through Locators. Playwright suggests prioritizing user-facing attributes and explicit contracts. This means that you might select a button by it’s user-facing label. Instead of selecting it by an implicit data-id.

const submit = page.getByRole("button", { name: "submit" });

⚡ Locators are similar to the get functionality from Cypress

An advanced example may further narrow down it’s selection by using filter

const product = page.getByRole("listitem").filter({ hasText: "Product A" });

// Filter + Chain + Action
await page
  .getByRole("listitem")
  .filter({ hasText: "Product A" })
  .getByRole("button", { name: "Add to cart" })
  .click();

There are a lot of ways to locate / select elements in Playwright. It’s suggested to use these built-in locators when possible. These locators create tests that are resilient and are the closest to how users and assistive technology perceive the page.

MethodDescription
page.getByRole(role)Locates elements based on their accessibility role (e.g., "button", "textbox").
page.getByText(text)Locates elements that contain the specified text content.
page.getByLabel(label)Locates a form control element by the associated label text.
page.getByPlaceholder(placeholder)Locates an input element by its placeholder text.
page.getByAltText(altText)Locates an element, typically an image, by its alternative text.
page.getByTitle(title)Locates an element by the value of its title attribute.
page.getByTestId(dataTestId)Locates an element based on the value of its data-testid attribute.

Actions

You can call an action from any locator (as shown in the previous section). For example, filling text in a form field, or checking a checkbox.

await page.getByLabel("Application Name").fill("Flotes App");
await page.getByLabel("I agree to the terms and conditions").check();
MethodDescription
locator.check()Check the input checkbox.
locator.click()Click the element.
locator.uncheck()Uncheck the input checkbox.
locator.hover()Hover mouse over the element.
locator.fill(text)Fill the form field with the specified text.
locator.focus()Focus the element.
locator.press(key)Press a single key (provide the key as an argument).
locator.setInputFiles(filepath)Select files to upload (provide the filepath as an argument).
locator.selectOption(value)Select an option in the dropdown menu based on its value.

Asserting

Playwright uses expect to write assertions. Playwright provides numerous matchers returned by page.expect(value).

The following are popular async matchers. These matchers are also known as auto-retry assertions, since they will wait until the condition is met.

// Alternatively, save the locator to a variable for reuse
await page.getByRole("button", { name: "submit" }).toBeVisible();
await page.getByRole("button", { name: "submit" }).toBeEnabled();
await page.getByRole("button", { name: "submit" }).toContainText("submit");
MethodDescription
await expect(locator).toBeChecked()Asserts the checkbox identified by the locator is checked.
await expect(locator).toBeEnabled()Asserts the control identified by the locator is enabled.
await expect(locator).toBeVisible()Asserts the element identified by the locator is visible.
await expect(locator).toContainText(text)Asserts the element identified by the locator contains the specified text.
await expect(locator).toHaveAttribute(name, value)Asserts the element identified by the locator has the specified attribute with the optional matching value.
await expect(locator).toHaveCount(count)Asserts the number of elements matching the locator is equal to the specified count.
await expect(locator).toHaveText(text)Asserts the text content of the element identified by the locator matches the specified text.
await expect(locator).toHaveValue(value)Asserts the value of the input element identified by the locator matches the specified value.
await expect(page).toHaveTitle(title)Asserts the title of the current page matches the specified title.
await expect(page).toHaveURL(url)Asserts the URL of the current page matches the specified URL.

⚡ Prefer auto-retry assertions, as it may prevent test flakiness.

Mocking APIs

Mocking APIs in e2e testing can create resilient stable tests that don’t fail based on network conditions or the state of the actual API.

To mock an API / End-Point in Playwright use page.route(glob, callback). When Playwright detects an API call that matches the URL glob passed to route, it will circumvent the actual http request and return a fake payload.

// Mock the end-point
await page.route("*/**/api/v1/fruits", async (route) => {
  const json = [{ name: "Strawberry", id: 21 }];
  await route.fulfill({ json });
});

// Waiting for a response
const responsePromise = page.waitForResponse("*/**/api/v1/fruits");
await page.getByText("Fruits").click();
const response = await responsePromise;
await page
  .getByRole("listitem")
  .filter({ hasText: "Strawberry" })
  .toBeVisible();

⚡ This is a simple example. Mocking APIs in Playwright is a feature with many possibilities / complexity.

Conclusion

Hopefully this article can act as a simple starting point for your journey into Playwright. Providing some initial clarity to the extensive documentation. If you have any comments, questions, or future topics you’d like to hear about, consider joining our discord.

Happy Programming :)

More Playwright

Enjoyed this article? - Try the Flotes version of this article!

🎭 Playwright Introduction

Tue Jul 09 2024