Your tests now have structure; today we make sure they actually prove something, because the assertion is the only part of a test that can catch a bug. Here is the version of “testing search” that I meet in real suites all the time. It runs, it is green, and it is decoration:
import { test, expect } from '@playwright/test';
test('search works', async ({ page }) => {
await page.goto('https://juliapottinger.com/practice/tumble-kitchen/');
await page.getByTestId('search-input').fill('patty');
await page.getByTestId('search-submit').click();
await expect(page.getByTestId('product-list')).toBeVisible();
});
Read it back: the test is named “search works” and it cannot tell you whether search works. The list it checks is on the page before you ever search. Search could return the wrong dish, the wrong price, or the whole menu for a query that matches one dish, and this stays green through every one of those failures. It also breaks the Day 7 rule by putting locators in the spec, which is fitting, because sloppy structure and sloppy assertions usually travel together.
The same test, written to be able to fail
import { test, expect } from '@playwright/test';
import { TumbleKitchenPage } from '../pages/tumble-kitchen-page';
test('verify that searching for patty returns exactly the cheese patty', async ({ page }) => {
const store = new TumbleKitchenPage(page);
await store.goto();
await store.searchFor('patty');
await expect(store.productCard).toHaveCount(1);
await expect(store.firstCard()).toContainText('Deluxe Plantain Plate');
await expect(store.priceOf(store.firstCard())).toHaveText('4,500 JMD');
await expect(store.page.getByTestId('products-status')).toHaveText('1 dish on the menu');
});
Read it back in behavioural terms. toHaveCount(1) fails if the search leaks extra dishes or finds none. toContainText fails if the wrong dish comes back. toHaveText holds the price to the exact string a customer sees, comma and all. And the status line assertion checks the store’s own count of what it found. Four assertions, four different ways the promise could break, each with a failure message that names the difference.
Choosing the strength of an assertion
- Exact, with toHaveText, when the value is a promise. A price, a total, an error message. Money is never “roughly right”.
- Contains, with toContainText, when the value has stable meaning inside changing packaging. The card also holds a description and a button; you care that the name is present.
- At least, for live data you do not own. If the catalogue grows next month, a hard toHaveCount(6) on the full listing breaks for a good reason that is not a bug. Assert the shape instead: at least one card is visible, and the dish you care about appears exactly once, with expect(store.productCard.filter({ hasText: ‘Jerk Chicken Plate’ })).toHaveCount(1).
The through-line: assert the outcome a customer would notice, at the strength the promise deserves.
If the assertion cannot fail while the promise is broken, it is not an assertion. It is decoration.
Do this today
Open every test you have written this week and interrogate the assertions: for each one, name a real failure it would catch, and a real failure it would sleep through. Tighten at least two. Then upgrade your search test to the four-assertion version above and run it green. Tomorrow: the store gets slow on purpose, and you learn to wait honestly.
