10.2 — The testing pyramid
9.7 planted a flag: “the testing pyramid — lesson 10.2.” Time to pay it. The idea: the same feature can be tested at three layers — and the layers differ in speed by a factor of a THOUSAND. Choosing the layer is therefore not a detail; it decides whether your suite runs in seconds (and gets run) or hours (and gets abandoned).
Three layers, real numbers, one famous shape — and the anti-pattern that eats teams alive.
// the SAME feature, asked at 3 layers
// (a checkout that applies a coupon)
// UNIT — one machine, isolated (~5ms)
expect(applyCoupon(1000, "SAVE10"))
.toBe(900);
// API — pieces wired, no browser (~300ms)
const res = await request.post("/api/checkout",
{ data: { total: 1000, coupon: "SAVE10" } });
expect(res.status()).toBe(200);
// E2E — whole system, real browser (~8s)
await page.fill("#coupon", "SAVE10");
await page.click("text=Apply");
await expect(page.locator(".total"))
.toHaveText("₹900");The code pane shows ONE feature — a checkout coupon — asked about at three layers. Same behavior under test, three completely different price tags. Read all three blocks once before we zoom in.
The deeper story, with the real names for things — this part is what turns “I saw it” into “I can explain it.”
The boundary between “unit” and “integration” is genuinely fuzzy and teams argue about it for sport — is a function plus its real helper two units or one integration? Don’t get stuck: the load-bearing distinction is touches the outside world or not (network, disk, browser, clock). Inside-only = fast lane; outside-touching = slow lane. 10.6’s test doubles exist precisely to move tests INTO the fast lane.
Real numbers behind the estimates: a browser E2E test pays for launching a browser context, loading a page, network round-trips (6.7), rendering (7.8), and auto-waiting pauses (11.6 will formalize them). None of that is waste — it’s exactly WHY it proves so much — but it can’t be made free, only paralleled (11.15).
The pyramid has respectable critics — you’ll meet the “testing trophy” (which fattens the integration band, arguing modern tools made it cheap). The disagreement is about the middle band’s size; NOBODY credible argues for the cone. Both camps agree on this lesson’s core: many fast tests below, few browser tests above.
Job note: “where would you test this?” is a beloved interview question. The winning shape of an answer is the one from the last step — name the layer, justify by cost and by what failure at that layer would tell you. You just practiced it three times.
⌨️ the suite health check
You inherit a test plan. Compute its shape and its runtime — and let the numbers, not opinions, say whether it’s a pyramid or a cone.
requirements:
- Create
plan: an array of 9 test objects —{ name: "…", layer: "unit" }× 6,layer: "api"× 2, andlayer: "e2e"× 1 (names are yours to invent). - Count each layer with a reusable
countLayer(plan, layer)(4.9’s filter + length) and print the shape line:6 unit · 2 api · 1 e2e(a template literal builds it). - Compute the total runtime in milliseconds — unit tests cost
5, api300, e2e8000— and print it as8630ms. - Print the verdict:
healthy pyramidwhen unit tests outnumber e2e tests,ice-cream coneotherwise.
when you press RUN, the console must show exactly:
✏️ Quick check 1
“Is the coupon math correct for 1000 minus 10%?” — which layer should ask this: unit, api, or e2e?
✏️ Quick check 2
A suite with 40 E2E tests and 3 unit tests has a famous nickname. What is it?
✏️ Quick check 3
500 unit tests at ~5ms each — roughly how many SECONDS is the whole run? (nearest whole number)
🗣️ Now teach it back
Draw the pyramid for a friend: define the three layers precisely, give the speed math that justifies the shape, explain what the ice-cream cone is and why teams fall into it, and say where Playwright sits.
Write it as if your friend is sitting next to you. Saved to your journal — future-you will use these notes to teach others.