JS Sketchbooksee JavaScript think ✏️
← back to phase 11

11.13 — Tags, selective runs & suite hygiene

Five hundred tests is not one thing — pre-merge wants a two-minute pulse check, nightly wants everything, and you want just the one you’re debugging. Tags, grep, honest skips, and the hygiene habits that separate a professional suite from a junk drawer — including the famous .only disaster and the config line that guards it.

watch it happen
test("checkout works @smoke", async ({ page }) => {…});
test("coupon math @regression", async ({ page }) => {…});

// $ npx playwright test --grep @smoke
// $ npx playwright test --grep-invert @slow

test.skip(({ browserName }) => browserName === "webkit",
  "drag flaky on webkit — issue #142");

test.fixme("drag-and-drop reorders", async () => {…});

test.only("the one I'm debugging", async () => {…});
// ⚠ NEVER commit — forbidOnly catches it on CI (11.3)

await test.step("add three items", async () => {…});
expect.soft(price).toBe(900);

The need first: different moments want different slices. Pre-merge: a fast pulse check, minutes. Nightly: the full matrix (11.12), hours are fine. Debugging: exactly one test, over and over. A suite you can only run WHOLE is a suite you’ll avoid running — selection is daily bread.

the suite as a card wall checkout works@smokelogin works@smokecoupon math@regressionsort by price@regressionbulk import@slowprofile update@regression500 tests is not ONE thing: pre-merge wants a 2-minute pulse check; nightlywants everything. Selection is a daily need
under the hood

The deeper story, with the real names for things — this part is what turns “I saw it” into “I can explain it.”

Playwright also supports tags as a first-class option — test("name", { tag: "@smoke" }, fn) — which reports can group by. The in-title convention and the option coexist; teams pick one style and stay consistent (the hygiene is the point, not the syntax).

Selection composes into PROJECTS (11.12): a smoke project with grep: /@smoke/ baked in means CI can run “the smoke lane” by name — which is exactly how 11.16’s pipeline will ask for it. Tags feed grep; grep feeds projects; projects feed CI. One convention, all the way up.

skip has more moods: test.skip(true, reason) unconditionally, test.skip(({ browserName }) => …, reason) conditionally per fixture values, and test.slow() — not a skip at all: it triples the timeout for a legitimately slow test, on the record.

Job note: suite hygiene is disproportionately VISIBLE seniority. Consistent tags, reasons on every skip, zero merged .onlys, steps in long tests — none of it is clever, all of it compounds. The engineer whose suite reads like a spec sheet gets trusted with the release button. That’s the actual promotion path hiding in this lesson.

your turn

⌨️ build --grep (and witness the .only disaster)

Two selection mechanisms, one lesson: a grep that filters the suite by tag, and an .only simulation that shows exactly how the disaster lies.

requirements:

  • Create suite: five test names — "checkout works @smoke", "login works @smoke", "coupon math @regression", "sort by price @regression", "bulk import @slow".
  • Write grep(suite, tag): the tests whose names include the tag (4.9 + 1.6’s includes). Run the smoke set: print each match, then 2 of 5 ran (computed from both lengths).
  • Simulate the disaster: only(suite, name) returns just the one matching test. Run it for "coupon math @regression" and print 1 of 5 ran — 4 silently skipped ⚠ — the counts computed.

when you press RUN, the console must show exactly:

checkout works @smoke
login works @smoke
2 of 5 ran
1 of 5 ran — 4 silently skipped ⚠

✏️ Quick check 1

A committed test.only reaches CI with forbidOnly enabled. What happens to the build?

✏️ Quick check 2

What defines a good smoke set? (roughly how many tests, covering what)

✏️ Quick check 3

expect.soft(price).toBe(900) fails mid-test. Does the test keep running, and does it still fail overall? (yes/yes, yes/no, …)

teach it back

🗣️ Now teach it back

Teach suite hygiene to a friend: tags and grep (what a smoke set is), honest skipping vs fixme, the .only disaster and its config guard, and what test.step and expect.soft each buy.

Write it as if your friend is sitting next to you. Saved to your journal — future-you will use these notes to teach others.

a few sentences, minimum — you’ve got this
to remember
Tags = words in titles (@smoke) — zero machinery; --grep runs the slice (4.9 on the suite), --grep-invert excludes; composes with --project. Smoke set = 10–15 critical-path tests proving the app breathes.
Skip HONESTLY: test.skip(condition, reason) — reported, reasoned, never silently green. fixme = known-broken, visible debt. test.slow() = tripled timeout, on the record.
The .only disaster: committed → suite silently shrinks → green while testing nothing; forbidOnly on CI (11.3) makes it fail loudly. test.step = named chapters (traces read as stories); expect.soft = gather all evidence, still fail at the end.