Playwright how to turn the test case easier to read

Halisson Skalee
4 min readApr 5, 2024

--

Playwright

Turn the test case easier to read

Currently, we are in the process of migrating from Cypress to Playwright. The old tests are kind of hard to read and based on this we decided to use the strategy of user-facing

Following the user-facing strategy

When testing websites, it’s important to focus on what users actually see and use. But sometimes, traditional testing methods rely on things like “data-testid,” which regular users can’t see. This can make tests less reliable and harder to understand.

Let’s look at an example: a login form on a website. It has fields for the user’s email and password, and a button to log in. Here’s a simple version of the HTML code:

<form id="loginForm">
<label for="username">email of your user:</label>
<input type="text" id="username" name="username" placeholder="Enter your username">

<label for="password">Password:</label>
<input type="password" id="password" name="password" placeholder="Enter your password">

<button type="submit">Login</button>
</form>

Our goal is to find the input field where users can type their email. But regular users can’t see things like “id” attributes. They only see the labels next to the input fields. So, to test based on what users see, we should use Playwright’s getByRole function to find the input field based on its label.

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

test('example', async ({ page }) => {
// This line finds the input field where users can type their email
const usernameInput = await page.getByRole('textbox', { name: 'email of your user:' });
});

It’s important to know that getByRole can find the input field because of the label. The label is connected to the input field using the "for" attribute. This way, our testing stays focused on what regular users can see and do on the website.

Mapping loop without unordered list

Could you find the mistaken on this code?

function UserList({ users }) {
return (
<div>
<h1>User List</h1>
<div>
{users.map((user, index) => (
<div className="user-card" key={index}>
<div className="user-info">
<div className="user-photo">
<img src={user.photo} alt={`User ${index + 1}`} />
</div>
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
</div>
<button className="edit-button">Edit</button>
</div>
))}
</div>
</div>
);
}

The issue with this code lies in the mapping loop, where the <div> element is chosen instead of <ul>. Because of this, you cannot find elements using the "user-facing" approach.

Here’s the corrected implementation of this component:

import React from 'react';

function UserList({ users }) {
return (
<div>
<h1>User List</h1>
<ul>
{users.map((user, index) => (
<li className="user-card" key={index}>
<div className="user-info">
<div className="user-photo">
<img src={user.photo} alt={`User ${index + 1}`} />
</div>
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
</div>
<button className="edit-button">Edit</button>
</li>
))}
</ul>
</div>
);
}
const users = [
{
name: 'User 1',
email: 'user1@example.com',
photo: 'user1.jpg',
},
{
name: 'User 2',
email: 'user2@example.com',
photo: 'user2.jpg',
},
{
name: 'User 3',
email: 'user3@example.com',
photo: 'user3.jpg',
},
];
export default function App() {
return <UserList users={users} />;
}

The goal is to create a test for this component to locate a unique user and click on their edit button to modify their name. Here you have the right testing implementation.

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

test('clickEditButtonForUser', async ({ page }) => {
// Use getByRole to find the list item containing the user email
const listItem = await page.getByRole('listitem').filter({ hasText: 'user2@example.com' });

// Use getByRole again to find the "Edit" button within the list item
const editButton = await listItem.getByRole('button', { name: 'Edit' });


await editButton.click();
})

Don’t use <img/> where there should be a <button/>

Picture this: you’re developing a component to modify the quantity of items in a shopping cart:

return (
<div>
<h1>Shopping Cart</h1>
<ul>
{items.map((item, index) => (
<li key={index}>
<div>{item.title}</div>
<div>
{/* Display quantity and buttons to change quantity */}
<span>Quantity: {quantities[index]}</span>
<img src="" onClick={() => increaseQuantity(index)} />
<img src="" onClick={() => decreaseQuantity(index)} />
</div>
</li>
))}
</ul>
</div>
);

The issue here is that we’re using an <img> tag like a button. To solve this, we should use a <button>, with the image icon inside it. It's crucial that the click event is attached to the button and not the image.

Don’t forget to use CSS to hide the button text, bord and whatever you need.

Here’s the corrected implementation:

import React, { useState } from 'react';

function ShoppingCart({ items }) {
// State to manage quantity for each item
const [quantities, setQuantities] = useState(items.map(item => item.quantity));
// Function to handle increasing quantity
const increaseQuantity = (index) => {
const newQuantities = [...quantities];
newQuantities[index]++;
setQuantities(newQuantities);
};
// Function to handle decreasing quantity
const decreaseQuantity = (index) => {
const newQuantities = [...quantities];
newQuantities[index]--;
setQuantities(newQuantities);
};
return (
<div>
<h1>Shopping Cart</h1>
<ul>
{items.map((item, index) => (
<li key={index}>
<div>{item.title}</div>
<div>
{/* Display quantity and buttons to change quantity */}
<span>Quantity: {quantities[index]}</span>
<button onClick={() => increaseQuantity(index)}>+</button>
<button onClick={() => decreaseQuantity(index)}>-</button>
</div>
</li>
))}
</ul>
</div>
);
}
// Example data for shopping cart items
const items = [
{ title: 'Item 1', quantity: 1 },
{ title: 'Item 2', quantity: 3 },
{ title: 'Item 3', quantity: 2 },
];
// Render the ShoppingCart component with example data
export default function App() {
return <ShoppingCart items={items} />;
}

The goal is to create a test to increase the quantity of the second item:

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

test('increaseQuantityForItem2', async ({ page }) => {
// Use getByRole to find the list item containing the item title
const listItem = await page.getByRole('listitem').filter({ hasText: 'Item 2' });

// Use getByRole again to find the "+" button within the list item
const increaseButton = await listItem.getByRole('button', { name: '+' });

// Click the "+" button to increase the quantity
await increaseButton.click();
});

--

--