Write and Run Your First Playwright Test with TypeScript

Now that Playwright is installed, it is time to write real tests. In this lesson you will understand every line of a Playwright test, automate a login page, run it in different ways, read a failure message, and view the HTML report.

Advertisement

๐Ÿ“– What You’ll Learn: test anatomy (test, expect, page fixture, async/await), writing a login test, running it from the CLI and VS Code, reading failure messages, negative tests with describe and beforeEach, the HTML report and baseURL.


๐Ÿงฑ Anatomy of a Playwright Test

Open tests/example.spec.ts from your project. Every Playwright test has the same building blocks:

import { test, expect } from '@playwright/test';      // 1. import test and expect

test('has title', async ({ page }) => {               // 2. test name + async function
  await page.goto('https://playwright.dev/');         // 3. actions use await
  await expect(page).toHaveTitle(/Playwright/);       // 4. assertion
});
  1. Import test (to declare tests) and expect (to make assertions) from @playwright/test.
  2. test(name, function): the name appears in reports, so make it describe the behaviour. The function is async because browser actions take time.
  3. { page } is a fixture: Playwright creates a fresh browser tab for each test and passes it in. You never launch the browser yourself.
  4. await in front of every action and assertion, so each step finishes before the next starts.
  5. expect(…) checks the result. Web-first assertions like toHaveTitle retry automatically until they pass or time out.

โœ๏ธ Write Your First Real Test: Login

We will automate the login page of Sauce Demo (https://www.saucedemo.com), a free practice shop. Create a new file tests/login.spec.ts:

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

test('standard user can log in', async ({ page }) => {
  // Arrange: open the login page
  await page.goto('https://www.saucedemo.com/');

  // Act: fill the form and submit
  await page.getByPlaceholder('Username').fill('standard_user');
  await page.getByPlaceholder('Password').fill('secret_sauce');
  await page.getByRole('button', { name: 'Login' }).click();

  // Assert: we are on the products page
  await expect(page).toHaveURL(/inventory\.html/);
  await expect(page.locator('.title')).toHaveText('Products');
});

What each line does:

  • page.goto(url) opens the page and waits for it to load.
  • page.getByPlaceholder('Username') finds the input by its placeholder text. This is a locator; you will master locators in Playwright Locators.
  • fill() clears the input and types the text.
  • getByRole('button', { name: 'Login' }) finds the button the way a user or screen reader sees it.
  • toHaveURL and toHaveText verify the outcome.

๐Ÿ’ก Tip: Structure tests as Arrange, Act, Assert: set up, perform the user action, then verify. It keeps tests easy to read.


โ–ถ๏ธ Run the Test

Run only this file, in Chromium, with the browser visible:

npx playwright test login.spec.ts --project=chromium --headed

Or run it from VS Code: open the Testing view, tick Show browser, and click โ–ถ next to the test name. When it passes you see a green tick; in the terminal you see:

Running 1 test using 1 worker
  โœ“  1 [chromium] โ€บ tests/login.spec.ts:3:5 โ€บ standard user can log in (1.9s)
  1 passed (2.6s)

โŒ Make It Fail on Purpose (and Read the Error)

Learning to read failures is as important as writing tests. Change the expected text to 'Product' (without the s) and run again. Playwright prints exactly what went wrong:

Error: Timed out 5000ms waiting for expect(locator).toHaveText(expected)

Locator: locator('.title')
Expected string: "Product"
Received string: "Products"

The message shows the locator, what was expected and what was received. Change it back to 'Products' so the test passes again.


๐Ÿงช Add a Negative Test and Group Tests

Good test suites cover failures too. Use test.describe to group related tests, and test.beforeEach to share setup:

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

test.describe('Login page', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('https://www.saucedemo.com/');
  });

  test('standard user can log in', async ({ page }) => {
    await page.getByPlaceholder('Username').fill('standard_user');
    await page.getByPlaceholder('Password').fill('secret_sauce');
    await page.getByRole('button', { name: 'Login' }).click();
    await expect(page).toHaveURL(/inventory\.html/);
  });

  test('locked out user sees an error', async ({ page }) => {
    await page.getByPlaceholder('Username').fill('locked_out_user');
    await page.getByPlaceholder('Password').fill('secret_sauce');
    await page.getByRole('button', { name: 'Login' }).click();
    await expect(page.locator('[data-test="error"]'))
      .toContainText('Sorry, this user has been locked out.');
  });

  test('empty form shows username required', async ({ page }) => {
    await page.getByRole('button', { name: 'Login' }).click();
    await expect(page.locator('[data-test="error"]')).toContainText('Username is required');
  });
});

Hooks such as beforeEach get a full lesson later in the course.


๐Ÿ“Š View the HTML Report

npx playwright test
npx playwright show-report

The report lists each test per browser, with its steps, duration and, for failures, the error, screenshot and trace (depending on your config). Click a test to see every action that ran.


๐ŸŒ Use baseURL to Shorten Your Tests

Instead of repeating the full URL, set baseURL in playwright.config.ts:

// playwright.config.ts (inside defineConfig)
use: {
  baseURL: 'https://www.saucedemo.com',
},

Now await page.goto('/') opens the home page. Learn every config option in playwright.config.ts explained.


โœ… Beginner Mistakes to Avoid

  • Forgetting await: the test may finish before the action happens. Always write await before page.* actions and expect for locators.
  • Using sleeps: do not add fixed waits; Playwright auto-waits. See Playwright waits.
  • Vague test names: 'test1' tells you nothing in a report. Describe the behaviour.
  • Tests that depend on each other: each test should work alone, in any order.

๐Ÿ“ Assignment

Use https://www.saucedemo.com for all tasks and put them in tests/login.spec.ts:

  1. Write a test that logs in as problem_user and verifies the products page title.
  2. Write a test that enters a wrong password and checks the error contains Username and password do not match.
  3. Write a test that logs in, adds Sauce Labs Backpack to the cart, and verifies the cart badge shows 1 (page.locator('.shopping_cart_badge')).
  4. Run all tests in Chromium with --headed, then in all browsers, and compare the HTML report.
  5. Bonus: make one test fail on purpose and write down the three pieces of information the error message gave you.

โ“ FAQ

๐Ÿค” How do I write my first Playwright test?

Create a .spec.ts file in the tests folder, import test and expect from @playwright/test, write test('name', async ({ page }) => { ... }) with actions and assertions, and run npx playwright test.

๐Ÿค” Why does every Playwright test use async and await?

Browser actions take time and return Promises. await makes each step finish before the next one runs. Learn more in TypeScript Functions and TypeScript for Playwright.

๐Ÿค” How do I run a single Playwright test?

Run npx playwright test login.spec.ts for one file, add -g "test name" to filter by title, or click โ–ถ in the VS Code Testing view. All options are in Playwright CLI commands.

๐Ÿค” Why does my test run three times?

The default config has three projects (Chromium, Firefox, WebKit), so each test runs once per browser. Use --project=chromium to run one browser.

๐Ÿค” How do I see the browser while the test runs?

Add --headed to the command, use --ui for UI mode, or tick Show browser in the VS Code extension.

๐ŸŽฌ Watch the full Playwright tutorial series on YouTube!

Subscribe to SqaToolsByDY • Practice on sqatools.in

Leave a Comment