Organizing Tests

By Syncfusion®, Inc. · Last updated

Grouping tests with test.describe, keeping tests independent, the four hooks and their order, test.step with boxed steps and return values, annotations (skip, fixme, fail, slow, conditional, only, custom), tags, picking tests with --grep, --only-changed and --last-failed, and generating tests from data.

Organizing Tests

  • One test is easy to read. Fifty tests in one file are not.
  • Playwright gives you tools to group tests, share setup, name steps and pick which tests run.
  • In this lesson, you will organize a growing test file for the Practice Shop.

Simple lesson: groups, hooks, steps and tags keep many tests easy to read and run.

A Real-World Example

  • Priya has three tests for the products page. Each one starts by opening the page.
  • She puts them in a group called "products page" and moves the shared first step into a hook.
  • The report shows each title as products page › shows six products.

What You Will Learn

  1. Groups with test.describe(), and why tests must stay independent.
  2. The four hooks and the order they run in.
  3. Steps with test.step().
  4. Annotations: skip, fixme, fail, slow and only.
  5. Tags, and running only some tests.
  6. Generating tests from data.

Test Structure

test.describe() and Nested Groups

  • test.describe('title', () => { ... }) puts tests in a named group.
  • Groups can sit inside groups. The titles join up in reports: cart › empty cart › shows a message.
  • Group by feature or page, not by test type.

Keeping Tests Independent

  • Every test gets a fresh page and a fresh browser context.
  • A test must never need another test to run first. Tests may run in any order, at the same time, or alone.
  • If two tests need the same setup, put it in a hook or a fixture, not in "test number one".

Simple lesson: group tests with describe, and make every test work on its own.

Hooks

The Four Hooks

HookRunsUse it for
test.beforeEach()before each test in its groupopening the page, logging in
test.afterEach()after each test, even a failed oneextra clean-up, logging
test.beforeAll()once, before the first test of its groupexpensive setup, such as test data
test.afterAll()once, after the last test of its groupdeleting that test data

Animation: the run order for a group with two tests: beforeAll, then beforeEach, test one, afterEach, then beforeEach, test two, afterEach, then afterAll

beforeAll beforeEach test 1 afterEach beforeEach test 2 afterEach afterAll

test.describe('cart', ...) with two tests Each hooks run around every test. All hooks run once around the whole group.

The orange marker shows what runs, in order.

  • Run this file and open the Terminal tab. The console.log lines show the real order.

Hook Scope in describe Blocks

  • A hook belongs to the group it is written in. Outside any group, it applies to the whole file.
  • Outer beforeEach hooks run before inner ones. Inner afterEach hooks run before outer ones.
  • beforeAll gets worker fixtures such as browser, but not page. Each test gets its own page.

When a test fails, Playwright throws away its worker and starts a new one. Then beforeAll runs again for the next test. Do not count on beforeAll running exactly once per file.

Simple lesson: beforeEach and afterEach wrap each test. beforeAll and afterAll wrap the group.

Test Steps

test.step()

  • test.step('title', async () => { ... }) names a part of a test.
  • Steps appear in the report, the trace and the lab's Steps tab. Long tests become easy to follow.

Step Subtitles and Params

  • test.step('Add to cart', fn, { subtitle: 'Yoga Mat' }) adds a short subtitle next to the step title in reports and traces.
  • params: { product: 'Yoga Mat' } attaches values that reporters can read. Both came in Playwright 1.63.

Boxed Steps

  • test.step('log in', async () => { ... }, { box: true }) makes a boxed step.
  • If something inside fails, the error points at the step, not at a line inside a helper.

Returning Values from Steps

  • A step can return a value: const total = await test.step('read the total', async () => { ... }).

Simple lesson: test.step() names the parts of a long test, and can return a value.

Annotations

AnnotationEffect
test.skip(title, body)Declares a test that does not run
test.skip(condition, 'why')Skips when the condition is true (inside a test or a group)
test.fixme(title, body)Like skip, but means "broken, to be fixed"
test.fail()Says the test should fail. Playwright reports a problem if it passes
test.slow()Gives the test three times the normal timeout
test.only(title, body)Runs only this test (and other only tests)

Conditional Annotations

  • Skip tests that do not apply to a browser or an environment:
test('payment uses the Chrome wallet', async ({ page, browserName }) => {
  test.skip(browserName !== 'chromium', 'The wallet exists in Chromium only');
  // ...
});

Custom Annotations

  • Add your own notes with the annotation option. They show in the HTML report.
  • Inside a test, test.info().annotations.push({ type: 'issue', description: '...' }) adds one at run time.
  • In WebKit, the second test is skipped. The fixme test never runs.
  • The last test fails as expected, so it counts as a pass. Its heading really is "404: Page not found".

Never commit test.only(). It silently turns off every other test. Set forbidOnly: !!process.env.CI in the config, and CI refuses to run a file with only in it.

Simple lesson: annotations skip, mark or focus tests, and explain why.

Tags

Adding Tags with the tag Option

  • A tag is a label that starts with @: @smoke, @slow, @checkout.
  • Add tags with the tag option: test('title', { tag: '@smoke' }, async ({ page }) => { ... }).

Tags in Test Titles and on describe Blocks

  • A word that starts with @ in a title is also a tag: test('login works @smoke', ...).
  • A tag on a describe block applies to every test inside it.

Simple lesson: tags label tests, so you can run a chosen set.

Selecting Tests to Run

--grep and --grep-invert

  • --grep @smoke (or -g) runs tests whose title or tags match.
  • --grep-invert @slow runs every test except those.
  • --grep takes a regular expression: --grep "@smoke|@checkout" runs both.

--only-changed and --last-failed

  • --last-failed runs only the tests that failed in the last run. Great while you fix them.
  • --only-changed runs tests in files that changed since the last git commit. It needs git, so it does not work in the lab.
  • Try these commands: --grep @checkout, --grep-invert @smoke, --list, then --grep "@smoke|@checkout".

Simple lesson: --grep and --grep-invert pick tests by title or tag. --last-failed re-runs failures.

Parameterized Tests

Generating Tests from Data

  • Write the test once, and run it for every item in a list.
  • A simple for loop around test() creates one test per item.

Titles for Generated Tests

  • Every generated test needs a different title. Put the data in the title.
  • Two tests with the same title in one file cause an error.

Keep data close to the test. If the list grows large, move it to a separate file and import it.

Simple lesson: loop over data to create one test per case, each with its own title.

Try It in Your Project

  • These steps take the lesson from the lab to your own app.
  • Group one spec file with test.describe() and move repeated setup into beforeEach.
  • Tag your three most important tests @smoke and run them with --grep @smoke.
  • Turn one copy-pasted set of tests into a data-driven loop.

Simple lesson: use one idea from this lesson on your own app this week.

TL;DR

  • test.describe() groups tests. Every test must work alone.
  • beforeEach/afterEach wrap each test. beforeAll/afterAll wrap a group.
  • test.step() names parts of a test, and can be boxed or return values.
  • skip, fixme, fail, slow and only change how tests run. Never commit only.
  • Tags plus --grep pick which tests run. --last-failed re-runs failures.
  • A loop around test() makes parameterized tests. Give each a unique title.

Common mistakes

MistakeWhat happensFix
Test 2 depends on data from test 1It fails when run alone or in another orderSet up data in a hook or inside the test
Using page in beforeAllPlaywright throws an errorUse beforeEach, or create a context from browser
Committing test.only()All other tests stop running on CIUse forbidOnly: !!process.env.CI
Same title for generated testsPlaywright reports duplicate titlesPut the data in each title
test.skip() with no reasonNobody knows why it is skippedAdd a description string

Check Yourself

Exercises

What to Remember

  • Group with describe. Keep each test independent.
  • Hooks: each around tests, all around groups.
  • Steps make long tests readable.
  • Annotations and tags change what runs. Never commit only.
  • Loops make data-driven tests with unique titles.

Next: Fixtures