During the workshop

Playwright workshop assignments.

Every assignment from the slides, in the same order. The UI workshop takes two evenings; the Going further assignments are for afterwards. Work at your own pace and open the hints when you get stuck.

Before you start

Complete the preparation and keep booker-platform running with npm run dev. The app runs on http://localhost:3000; log in with user or admin, password password123.

Assignments · UI · Evening 1

Your first stable tests.

You build your own test suite for the booker-platform web app over two evenings. Tonight: a first test, then make it pass every time. Open the UI slides.

  1. Assignment 1A · Your first test

    Create playwright/tests/ui/register.spec.ts. Start on the login page (/login):

    • Follow the link to Register and register a new user
    • Log in with that user
    • Assert that "No bookings yet" is shown

    Write it yourself first, then compare with what codegen records. Prefer getByRole and getByLabel, and add a web-first assertion to every step. Expect a strict mode violation or two: read the error before you fix it.

    Hints
    • Start the recorder with npx playwright codegen localhost:3000/login, or use "Record new" in the VS Code extension.
    • Registering only works after you check getByLabel('I accept the terms and conditions').
    • Strict mode violation on getByLabel('Password')? Names match substrings, and "Confirm password" contains "password". Add { exact: true }.
    • Two "Register" links on the login page? One is in the header. Scope to the page content: page.getByRole('main').getByRole('link', { name: 'Register' }).
    • Web-first assertions wait for you: await expect(locator).toBeVisible().
  2. Assignment 1B · Debug & report

    • Configure the HTML reporter and set trace: 'on'
    • Duplicate your test from 1A a few times and enable fullyParallel
    • Run with --repeat-each 5. What breaks, and why?
    • Fix it with unique test data per test
    • Open the report and a trace: which step is the slowest?
    Hints
    • Open the report with npx playwright show-report; click a test to open its trace.
    • Every test registers the same user, and usernames must be unique.
    • createRandomUser() in playwright/support/datafactories/user.factory.ts makes a new user every time.
Before evening 2

Finish 1A and 1B, and run git pull in booker-platform: it has starter files for assignment 2.

Assignments · UI · Evening 2

Scale up your suite.

Page objects and fixtures, a shared login, control over the network, and a suite that stays green when the app misbehaves.

  1. Assignment 2 · Page objects & fixtures

    • Finish the starter page objects in playwright/support/pages/: LoginPage and RegisterPage
    • Refactor your test from 1A to use them
    • Expose them as fixtures in playwright/support/fixtures/ui.fixture.ts
    • Bonus: add a BookingWizard page object, book a room and verify it in My bookings
    Hints
    • Put locators in the constructor and user actions (login(username, password)) in methods. Keep assertions in the test.
    • Import test and expect from ui.fixture.ts in every spec.
    • The payment form in the wizard accepts card 4242 4242 4242 4242 with any future expiry date and any CVC.
  2. Assignment 3 · Authentication

    • Write auth.setup.ts that saves the state for user and admin
    • Add ui-user and ui-admin projects that depend on setup
    • Make your tests start logged in. Compare the duration in the report: faster?
    • Bonus: log in with POST /api/auth/login and set booker.token instead of using the login form
    Hints
    • Save the state with page.context().storageState({ path }) and load it with use: { storageState } in the project.
    • Add playwright/.auth to .gitignore.
    • Start logged out in one file: test.use({ storageState: { cookies: [], origins: [] } }).
    • Bonus: open a page first, then page.localStorage.setItem('booker.token', token).
  3. Assignment 4 · Network & API

    Network

    • Mock POST /api/bookings to return 409 and assert the "Room not available" alert
    • Bonus: patch the rooms response so a fake room shows up

    Hybrid

    • Seed a room (as admin) and a booking (as user) via the API, then verify them in the UI
    • Or book in the UI and verify via GET /api/bookings/my-bookings
    • Bonus: move the seeding into a fixture
    Hints
    • Mock with page.route() and route.fulfill({ status, json }). Register the route before the request happens.
    • Patch a real response: const response = await route.fetch(), then route.fulfill({ response, json }). Copy a real room and change its number, so every field is there.
    • Use the request fixture for API calls. The API docs are at /api-docs.
    • Run your hybrid test twice. A 409 the second time? The dates are still booked: clean up, or let a fixture do it.
  4. Assignment 5 · Flaky-test clinic

    The trainer switches on chaos mode: slow rooms, flaky bookings, random order and a cookie pop-up.

    • Make your suite green again
    • Without waitForTimeout and without adding retries
    • Use what you learned: traces, web-first assertions, locators by name, addLocatorHandler, toPass

    Practising on your own? Add a chaos project to playwright.config.ts and run npx playwright test --project chaos:

    {
      name: 'chaos',
      use: {
        extraHTTPHeaders: {
          'x-booker-flags': 'slow-rooms,flaky-booking,random-order,popup-cookie',
        },
      },
    }
    Hints
    • Open a trace of a failing test first. What did the page look like?
    • page.addLocatorHandler() clicks the cookie pop-up ("We value your privacy") away whenever it shows up.
    • Random order: find rooms by name, not by position.
    • Flaky bookings fail before the booking is saved, so it's safe to retry just that step with expect(...).toPass().

Assignments · UI · Going further

After the workshop.

Self-study: one assignment per topic in the Going further part of the UI slides.

  1. Assignment 6 · Time, contexts & environment

    Pick one (or more):

    • Cancel a booking (confirm dialog) and download its invoice; check the file name and content
    • Two contexts: a guest books, the admin approves, the guest sees the status change
    • page.clock: the check-in countdown on My bookings reaches "is open" at 15:00
    • Add a mobile project and run your tests with the nl-NL locale and dark mode
    • Admin: upload a room image and reorder the rooms with drag & drop
    • Open the terms in a new tab, then pay in the payment widget (shadow DOM)
    Hints
    • Downloads: start page.waitForEvent('download') before you click.
    • Contexts: browser.newContext({ storageState: 'playwright/.auth/admin.json' }) gives you a second user in the same test. Declare it with await using, so it's closed even when the test fails.
    • Clock: use page.clock.setFixedTime(). With install() plus pauseAt() the app's timers stop too, and the page keeps loading.
    • Mobile: combine devices['Pixel 7'] with locale and colorScheme.
    • Upload with setInputFiles(); playwright/test-data/avatar.png is a test image. Drag with locator.dragTo().
    • New tab: context.waitForEvent('page'). The payment form is in an open shadow root, so ordinary locators reach it.
    • Test card 4242 4242 4242 4242 (any future date and CVC) is accepted; 4000 0000 0000 0002 is declined.
  2. Assignment 7 · Visual regression

    • Take a screenshot of the home page; run the test twice. Does it pass?
    • Mask the "deal of the day" banner and update the baseline
    • Then hide the banner with stylePath instead. Which do you prefer?
    • Now open the page with ?test=1 (test mode). Do you still need either?
    • Bonus: switch the snapshot to WebP and compare the file size
    Hints
    • await expect(page).toHaveScreenshot('home.png', { mask: [locator] })
    • Update the baselines with npx playwright test --update-snapshots.
    • Baselines are per operating system, so a baseline from your laptop won't match in CI.
  3. Assignment 8 · Accessibility

    • Run an axe scan on each step of the booking wizard. How many violations?
    • Attach the results to the report with testInfo.attach()
    • Bonus: add an aria snapshot of the main navigation and the booking summary
    Hints
    • new AxeBuilder({ page }).analyze() from @axe-core/playwright.
    • Aria snapshots: await expect(locator).toMatchAriaSnapshot(); let Playwright write the first version with --update-snapshots.
  4. Assignment 9 · Continuous integration

    • Fork booker-platform and add a GitHub Actions workflow that runs your UI tests
    • Upload the HTML report as an artifact; download it and open a trace
    • Bonus: shard over two jobs and merge the reports
    Hints
    • In CI, run npx playwright install --with-deps and npm run setup before the tests.
    • Upload the report with actions/upload-artifact and if: ${{ !cancelled() }}, so you also get it when tests fail.
    • No need to unzip the downloaded artifact: npx playwright show-report playwright-report.zip opens it.
    • Sharding: --shard 1/2 with the blob reporter, then npx playwright merge-reports.
  5. Assignment 10 · AI-assisted testing (optional)

    • Run npx playwright init-agents in booker-platform
    • Let the planner write a plan for "cancel a booking", then generate the test
    • Review the result like a colleague's pull request. Would you merge it?
    Review checklist
    • Are the assertions meaningful?
    • Does it use role-based locators?
    • Is the test data isolated?
    • No waitForTimeout?

Assignments · API

API testing with Playwright.

You test the booker-platform REST API directly. The starter files are in playwright/tests/api; the Swagger docs are at http://localhost:3000/api-docs. Open the API slides.

  1. Assignment 1 · Authentication

    • Open playwright/tests/api/assignment1.spec.ts
    • Register a new (random) user
    • Log in with that user and store the token
    • Log out with that token
    • Assert status codes, headers and the response body

    Read the Swagger docs and the README. When in doubt: ask 😉

  2. Assignment 2 · Rooms & bookings

    • Continue with playwright/tests/api/assignment2.spec.ts
    • Find a room and conclude none is to your liking
    • Add a new room as admin. Construction will surely be done before you go 🙂
    • Book your new room as a regular user
    • The kids bring the flu home from daycare: cancel the booking and verify it's cancelled

    Tip: the API rejects bookings in the past and bookings that overlap. Check the status codes in Swagger.

  3. Bonus · Improve the setup

    Start from playwright/tests/api/bonus.spec.ts (a copy of assignment 2):

    • How could we improve our setup?
    • Can we make our tests more readable? (fixtures, helpers)
    • Can we reuse the same authenticated state for all tests?
    • Can we check that every response is structured correctly, without validating it in every test?

After the workshop

Compare with the solutions.

Reference solutions for every assignment are on the solutions branch of booker-platform. Try it yourself first: there's more than one good answer.

git fetch origin && git switch solutions