Testing Important Features

What Should We Test?

Let’s think about what kind of features are most important to test:

  1. Login and Register

    • Why? Because users can’t do anything without an account
    • If these break, nobody can use our site
  2. Important Actions (like shopping cart or posting)

    • Why? These are the main things users want to do
    • If these break, users get frustrated and leave
  3. Search and Navigation

    • Why? Users need to find things on our site
    • If these break, users get lost

We will look at writing some e2e tests for the example repo.

Setting Up baseURL

First, we need to tell Playwright where to find our website. In your config file set the baseUrl:

bash
	// playwright.config.js
	export default defineConfig({
	  use: {
	    baseURL: 'http://localhost:5500'
	  }
	});

Now when you use, for example, page.goto('/auth/login') in your tests, Playwright will go to http://localhost:5500/auth/login.

To make this work, we need a local server running our site during tests. Let’s add live-server:

bash
	npm i live-server -D

Add a script to your package.json:

json
	{
	  "scripts": {
	    "start": "live-server --port=5500"  
	  }
	}

Update your Playwright config to start the server automatically before running tests:

javascript
	export default defineConfig({
	  webServer: {
	    command: 'npm run start',
	    url: 'http://localhost:5500',
	    reuseExistingServer: !process.env.CI,
	  },
	  use: {
	    baseURL: 'http://localhost:5500'
	  }
	});

This way, Playwright will start your local server before running tests and shut it down when they’re done. The reuseExistingServer option tells Playwright to use any running server when testing locally, but always start a fresh one in CI environments.

Using Environment Variables

Even though we’re using vanilla JavaScript in the example repo, we can use environment variables in our Playwright tests since they run in Node.js.

When testing, we often need to use things like emails, passwords or API keys. Never put these directly in your test files, rather use environment variables.

  1. Install dotenv:
bash
	npm install dotenv --save-dev
  1. Create a .env file in the root of your project and add variables you require in your tests, for example:
bash
	TEST_USER_EMAIL=workflowuser@stud.noroff.no
	TEST_USER_PASSWORD=workflowpass
  1. Make sure .env is in your .gitignore:
bash
	node_modules
	.env
  1. In playwright.config.js configure dotenv:
javascript
	require("dotenv").config();
  1. Now we can access the variables in our tests like this:
javascript
	process.env.TEST_USER_EMAIL;

Login test

Now that we’ve configured everything, let’s write our login test.

Our test will check two things:

  1. Happy Path: Everything works

    • User types correct email and password
    • User gets logged in successfully
  2. Error Path: Wrong password

    • User types correct email but wrong password
    • User sees error message

Here’s the code:

javascript
	import { test, expect } from "@playwright/test";
	
	test.describe("login", () => {
	  test("user can login", async ({ page }) => {
	    // Go to login page
	    await page.goto("/auth/login");
	
	    // Fill in form using name attributes
	    await page.locator('input[name="email"]').fill(process.env.TEST_USER_EMAIL);
	    await page
	      .locator('input[name="password"]')
	      .fill(process.env.TEST_USER_PASSWORD);
	
	    // Click login
	    await page.getByRole("button", { name: "Login" }).click();
	
	    // Check if we see logout button - means we're logged in
	    await expect(page.getByRole("button", { name: "Logout" })).toBeVisible();
	  });
	
	  test("wrong password shows error", async ({ page }) => {
	    await page.goto("/auth/login");
	
	    await page.locator('input[name="email"]').fill(process.env.TEST_USER_EMAIL);
	    await page.locator('input[name="password"]').fill("wrongpassword");
	
	    await page.getByRole("button", { name: "Login" }).click();
	
	    // Check for error in message container
	    await expect(page.locator("#message-container")).toContainText(
	      "Invalid email or password"
	    );
	  });
	});

Understanding the Login Test

In each test, we:

  • Go to the login page
  • Fill in the form fields
  • Click the login button
  • Check if we got the result we expected

Similar to Vitest, we group tests within a describe block.

Ways we can find things on the page:

  • locator('input[name="email"]') finds input boxes by their name
  • getByRole("button") finds buttons by their text
  • locator("#message-container") finds things by their ID

How we check results:

  • For success: Look for the Logout button
  • For failure: Look for the error message

That’s it - we’re just copying what a real user would do: visit the page, fill in the form, click the button, check what happened.

Understanding test.describe and page

In our test above, we used some special Playwright features:

  1. test.describe is how we group related tests in Playwright:

    javascript
    	test.describe("login", () => {
    	  // login-related tests go here
    	});
  2. The { page } argument:

    javascript
    	test("user can login", async ({ page }) => {
    	  // test code using page
    	});

Each test gets a page object that lets us control the browser - we use it to:

  • Go to pages: page.goto()
  • Find things: page.locator()
  • Click things: page.click()
  • Check things: expect(page.locator())

Running Your Tests

In a terminal, run the tests:

bash
	npx playwright test

To see the tests running in the browser:

bash
	npx playwright test --headed

To see a report of what happened:

bash
	npx playwright show-report

This shows you which tests passed, failed, and why.

What We Learned

In this lesson, we:

  • Identified which features are most important to test (login/register, key actions)
  • Set up Playwright with a baseURL for easier testing
  • Learned how to use environment variables to keep test credentials secure
  • Created real-world login tests that check both successful and failed login attempts
  • Discovered different ways to find elements on a page:
    • By input name: locator('input[name="email"]')
    • By button text: getByRole("button", { name: "Login" })
    • By ID: locator("#message-container")

Remember:

  • Always test both successful and error cases
  • Keep sensitive information in .env files
  • Write tests that follow what real users would do
  • Check for visible changes on the page to confirm test results
  • Use meaningful test descriptions that explain what you’re testing