Skip to the content

Module 1: What testing is, and why a real browser

Module 2: What a browser does when you click

Module 3: Setting up Playwright

Module 4: Your first test, line by line

Module 5: Running tests and reading the results

Module 6: Finding elements with locators

Module 7: Narrowing and combining locators

Module 8: Acting like a user, and auto-waiting

Module 9: Assertions that wait

Module 10: Organising tests: groups, hooks, steps and tags

Module 11: Configuration, projects and devices

Module 12: Fixtures: what a test is given

Module 13: Page objects and reusable helpers

Module 14: The network: stand-ins and API tests

Module 15: Signing in once: authentication and storage state

Module 16: Screenshots and accessibility checks

Module 17: Parallel runs, sharding and CI

Module 18: Flaky tests: root causes and real fixes

Module 19: Designing a suite that scales

Module 20: Mastery challenge

Module 21: Final project: tests for a room booking app

Answer key

  1. 1. What testing is, and why a real browser · A test does three things: prepare, act, check · Put in orderPut the parts of a test for "adding a product to the cart" in order.1. Open the shop’s product page 2. Click "Add to cart" 3. Check the cart badge now says 1Arrange (open the page), act (click), assert (check the badge). Checking before acting would test nothing, because the badge has not had a reason to change yet.
  2. 1. What testing is, and why a real browser · Many small tests, fewer big ones · QuizYou want to be sure a shopper can sign in, add a mug to the cart and reach the payment page. Which kind of test fits best?B. An end-to-end testIt is a journey across several pages, so it needs a real browser driving the whole app: an end-to-end test. A unit test could check the cart total, but not that the pages link up. Checking by hand once does not protect the next change.
  3. 1. What testing is, and why a real browser · Playwright drives real browsers from code · QuizWhich browser engine does Playwright use to stand in for Safari?C. WebKitSafari is built on WebKit, so Playwright runs WebKit to show how pages behave there. Chromium is the engine behind Chrome and Edge; Firefox is its own engine.
  4. 1. What testing is, and why a real browser · What makes a test worth having · QuizA test passes on Monday, fails on Tuesday, and passes again on Wednesday. Nobody changed the app. What is it?B. A flaky testNothing changed, yet the result did: that is flakiness. Later modules show where it comes from (timing, shared data, the network) and how Playwright helps avoid it.
  5. 2. What a browser does when you click · JavaScript changes the page while you watch · QuizA test types "Buy milk", presses Enter, and checks the list immediately, without waiting. The app saves to the server before showing the item. What can happen?B. The check sometimes fails, because the item is not there yetThe check races the app. When the server is fast it wins, when it is slow it loses: a flaky test. Playwright solves this by retrying checks until they pass or time runs out, which module 9 covers.
  6. 2. What a browser does when you click · Practise: roles and names · QuizWhat is the role and name of the text box here?B. Role "textbox", name "Password"An <input> that takes text has the role textbox, and the <label> pointing at it (by for="pw") gives it the name "Password". The id is for the code, not for people, so it is not the name. A password box is still a textbox to the accessibility tree.
  7. 2. What a browser does when you click · Practise: roles and names · QuizA button shows only a trash-can picture and has no text. What does a screen reader announce as its name?B. Nothing useful: it has no nameThe browser cannot guess what a picture means. Without text or an aria-label="Delete", the button has no name: a real accessibility bug, and one a role-based test catches because it cannot find the button by name.
  8. 3. Setting up Playwright · What the starter creates · QuizWhich file will Playwright run as tests by default?B. tests/cart.spec.tsBy default Playwright looks for files whose names end in .spec or .test followed by a JavaScript or TypeScript extension. cart.ts could hold helper code; it is not run as tests on its own.
  9. 3. Setting up Playwright · Headless: a browser with no window · QuizIs a headless browser a "real" browser?B. Yes: it runs scripts, styles and layout; it just does not show a windowHeadless mode skips putting pixels in a window, not the work of a browser. Layout, scripts, events and screenshots all happen. That is why tests pass the same way headed or headless.
  10. 3. Setting up Playwright · Try it · Fill the blanksInstall only the Chromium browser for this Playwright version.1: playwright, 2: installnpx playwright runs the Playwright command line installed in the project; install downloads browsers. Naming one browser saves time and disk space when that is all you test.
  11. 4. Your first test, line by line · Why every line starts with await · Find the bugThis test fails with "Error: Test ended." even when the to-do is added. Which line is wrong?Line 6: await expect(page.getByTestId('todo-title')).toHaveText('Buy milk')Without await, the test does not wait for the check: the function ends while the check is still running, so Playwright stops it and fails the test with "Test ended.", whether the page is right or not. Higher up in a test, a missing await lets the next lines run before the check has finished. Playwright’s checks on locators always need await.
  12. 4. Your first test, line by line · Running it · QuizYou wrote one test, and the summary says "3 passed". Why?B. The config runs each test once per browser: Chromium, Firefox and WebKitThe starter playwright.config.ts defines three projects, one per browser engine, and every test runs in each. Module 11 shows how to choose which ones run.
  13. 4. Your first test, line by line · Practise: write a check · Fill the blanksComplete a test that opens the to-do app and adds "Walk the dog".1: page, 2: goto, 3: fill, 4: Enter, 5: expectAsk for page, open the address with goto, type with fill, press Enter to submit, then expect the title. The same shape works for nearly every test you will write.
  14. 4. Your first test, line by line · Practise: write a check · QuizWhen does page.getByPlaceholder('What needs to be done?') look for the element?B. Only when you use it: in an action like fill or a check like expectA locator is a recipe, not a found element. It searches each time you act on it, so it always finds the current element, even if the page has redrawn it. That is why creating one needs no await.
  15. 5. Running tests and reading the results · Choosing which tests run · QuizWhich command runs only the tests whose name mentions "sign in"?B. npx playwright test -g "sign in"-g (short for --grep) filters by test name. A bare word after test filters by file path, so sign-in matches a file like tests/sign-in.spec.ts, not a test name. --project picks a browser setup.
  16. 5. Running tests and reading the results · UI mode: watch, rerun, and step back in time · QuizA test fails at the 6th step and you want to see what the page looked like just before. What do you use?B. UI mode: click the 6th action to see the snapshot before and afterUI mode records a snapshot for every action, so you travel back to any moment. Pauses only slow the test down and still do not show you the page’s inside.
  17. 5. Running tests and reading the results · Traces: a recording of a test run · Put in orderA test fails. Put a sensible way to investigate in order.1. Read the error: which check, expected, received 2. Open the report and look at the failed step 3. Open the trace or UI mode and look at the page at that step 4. Fix the test or the app, then run that test againStart with the cheapest information, the error message, and only dig into the trace when you need to see the page. Rerunning the single test (by file and line) keeps the loop short.
  18. 6. Finding elements with locators · Why role first · QuizA designer renames the class btn-blue to btn-primary. Which locator still works?B. page.getByRole('button', { name: 'Sign in' })The CSS selector names a class, which is a styling detail, so it breaks. The role and name are what the user perceives, and the rename did not change those.
  19. 6. Finding elements with locators · CSS and XPath, when you must · QuizWhich locator is the best choice for the shop’s search box, <input aria-label="Search products">?C. page.getByRole('textbox', { name: 'Search products' })A plain <input> (no type="search") has the role textbox, and aria-label gives it its name. The role searchbox is only for type="search" or an explicit role="searchbox". locator('input') would match every input on the page.
  20. 6. Finding elements with locators · Strictness: one action, one element · QuizThe page has buttons "Add to cart" and "Add to wishlist". What does page.getByRole('button', { name: 'Add' }).click() do?B. Fails with a strict mode violation: it matches bothNames match on a part of the text by default, so "Add" matches both buttons, and actions refuse to choose. Use the full name, or { name: 'Add', exact: true } if a button is really called just "Add".
  21. 7. Narrowing and combining locators · filter keeps the matches you want · Fill the blanksKeep only the products that are not sold out, then check there are two.1: filter, 2: hasNotText, 3: toHaveCountfilter with hasNotText drops the teapot, which says "Sold out". toHaveCount checks how many elements a locator matches, and retries until it is right, like every expect on a locator.
  22. 7. Narrowing and combining locators · Checking a whole list at once · QuizThe page shows "Blue teapot, Red mug, Green bowl". Does the check above pass?B. No: the array must match in orderAn array checks each element against the same position. It also fails if there are more or fewer matches than items in the array. To ignore order, sort or check each item separately; to check only part of each text, use toContainText.
  23. 7. Narrowing and combining locators · Practise: the right product · Find the bugThe test should add the Green bowl, but the cart ends up with a Red mug. Which line is wrong?Line 4: await bowl.getByRole('button', { name: 'Add to cart' })Line 4 searches inside all products, not inside bowl, and first() then picks the mug. Chain from bowl so the search stays inside the right item, and the first() is no longer needed.
  24. 7. Narrowing and combining locators · Practise: the right product · QuizWhat is the difference between these two?B. A finds the list item that contains "Sold out"; B finds the "Sold out" text itselffilter keeps the outer elements (the items) that contain a match. Chaining moves into them and returns the inner match (the paragraph). Use A to then click that item’s button; use B to check the label itself.
  25. 8. Acting like a user, and auto-waiting · Pressing keys · QuizWhy write ControlOrMeta+a instead of Control+a?B. It presses Control on Windows and Linux, and Command on macOSShortcuts use Command on a Mac and Control elsewhere. ControlOrMeta picks the right one for the computer running the test, so one test works for both.
  26. 8. Acting like a user, and auto-waiting · Checkboxes, menus, and files · Fill the blanksFill the checkout: choose standard delivery and accept the terms.1: radio, 2: check, 3: check, 4: clickRadio buttons have the role radio. check() works for both radios and checkboxes, and it verifies the box really is checked afterwards, which click() does not.
  27. 8. Acting like a user, and auto-waiting · When an action times out, the log says why · QuizWhat stops the click above?B. A cookie banner covers the button"intercepts pointer events" means another element is on top, at the spot Playwright would click. A real person could not click it either. Close the banner first in the test, or set a cookie so it never shows (module 15).
  28. 8. Acting like a user, and auto-waiting · Clicks that change the page · Put in orderPut what happens during button.click() in order.1. Find the element the locator describes 2. Wait until it is visible, stable and enabled 3. Scroll it into view 4. Check nothing covers its middle, then press and release the mouse there 5. Return once the click is doneFinding comes first, then the actionability checks, then scrolling and the real mouse events. If the element changes during the checks (redrawn, say), Playwright starts again with the fresh element.
  29. 9. Assertions that wait · Saying what should not be there · QuizThe cart line reads "Red mug × 2". Which of these pass?B. B and CA string in toHaveText must match the whole text, so A fails on the "× 2". toContainText checks for a part (B), and a regular expression matches anywhere unless anchored with ^ and $ (C).
  30. 9. Assertions that wait · Plain values do not retry · Find the bugThis check is flaky: the to-dos load a moment after the page. Which line is the problem?Line 3: await expect(items).toHaveCount(3)count() reads the number once, right now, and the plain toBe cannot retry. If the list has not loaded, it sees 0. await expect(items).toHaveCount(3) keeps checking until there are three.
  31. 9. Assertions that wait · Waiting for anything: expect.poll and toPass · QuizYou need to wait until a text box shows "Saved". Which do you use?B. await expect(box).toHaveValue('Saved')There is a built-in web-first assertion for exactly this, with a clearer error. Reach for poll and toPass only when no locator assertion fits.
  32. 10. Organising tests: groups, hooks, steps and tags · Each test stands on its own · QuizTest A adds a mug to the cart, which the shop keeps in the browser’s storage. Test B runs next and opens the cart. What does it see?B. An empty cart: B has a new contextBrowser storage belongs to a context, and B gets a fresh one. (If the shop saved the cart on its server, B could see it: that is shared data, which module 19 deals with.)
  33. 10. Organising tests: groups, hooks, steps and tags · Practise: hooks · Put in orderA file has a beforeAll, a beforeEach, an afterEach and two tests. Put what runs in order (one worker).1. beforeAll 2. beforeEach, then test 1 3. afterEach for test 1 4. beforeEach, then test 2 5. afterEach for test 2beforeAll runs once, first. beforeEach and afterEach wrap each test. If tests are split across workers, each worker runs its own beforeAll, which is why it must not assume it runs only once in total.
  34. 10. Organising tests: groups, hooks, steps and tags · Practise: hooks · Fill the blanksOpen the shop’s product page before each test.1: beforeEach, 2: pagetest.beforeEach takes the same fixtures as a test, so it can ask for page and open the address.
  35. 10. Organising tests: groups, hooks, steps and tags · Tags pick out groups of tests · QuizWhat does npx playwright test --grep-invert @slow run?B. Every test except those tagged @slow--grep keeps matching tests; --grep-invert drops them. Tags are matched like test names, so the same flags work for both.
  36. 11. Configuration, projects and devices · Projects run the suite in several setups · QuizWith the four projects above and 10 tests, how many test runs happen with npx playwright test --project=phone?B. 10Without the flag each of the 10 tests runs in all 4 projects: 40 runs. --project=phone keeps only one project, so 10.
  37. 11. Configuration, projects and devices · Practise: write a config · Fill the blanksRun against a local shop, keep traces on retry, and add a phone project.1: baseURL, 2: on-first-retry, 3: projects, 4: devicesbaseURL and trace go under use, since they shape the browser; projects sits at the top level and each project has its own use.
  38. 11. Configuration, projects and devices · Practise: write a config · Find the bugThe tests still open at 1280 pixels wide. Which line is in the wrong place?Line 3: use: { baseURL: 'http://localhost:3000', viewport: { width: 390, height: 844 } },viewport is a browser option, so it belongs inside use. At the top level it is ignored (TypeScript flags it, too, which is one reason to write the config in TypeScript).
  39. 12. Fixtures: what a test is given · Fixtures are made only when asked for · QuizA test only asks for request, to call an API. Does Playwright open a browser tab for it?B. No: fixtures are only set up when a test names themFixtures are lazy. Pure API tests that never name page run without a tab, which makes them very fast.
  40. 12. Fixtures: what a test is given · Using a custom fixture · Fill the blanksFinish a fixture that opens the cart page and hands it to the test.1: extend, 2: page, 3: use, 4: useThe fixture asks for page, does its setup, then passes the value to use. Leave out the use(…) call and the test fails with "use() was not called in fixture"; leave out only its await, and any cleanup after it runs while the test is still going.
  41. 12. Fixtures: what a test is given · Fixtures or hooks? · Find the bugTests using this fixture sometimes see the cart already emptied. Which line is wrong?Line 5: await use(page)Without await, the fixture does not wait for the test. It goes straight on to the cleanup and empties the cart while the test is still running.
  42. 13. Page objects and reusable helpers · Tests read like the plan · QuizThe placeholder changes to "Add a task". With the page object, how many places need editing?A. One: the constructorEvery test reaches the box through todos.newTodo, which is defined once. That single edit is the main reason page objects exist.
  43. 13. Page objects and reusable helpers · Offer page objects as fixtures · Fill the blanksUse the todos fixture in a test.1: todos, 2: add, 3: itemsThe test names the fixture, calls the page object’s method, and asserts on one of its locators. No setup lines are left in the test.
  44. 13. Page objects and reusable helpers · When abstraction hurts · Find the bugTypeScript rejects this page object. Which line?Line 4: // move navigation into an async goto() methodA constructor cannot be async, so it cannot await. Keep constructors to building locators, and put navigation in an async goto() method (or in the fixture).
  45. 13. Page objects and reusable helpers · Small helper functions are fine too · QuizOnly two tests share one three-line step. What is the simplest good option?B. A small helper functionA function names the step and keeps it in one place, without a class. Promote it to a page object if more steps for that page pile up. Copying is fine for two lines, but steps tend to grow.
  46. 14. The network: stand-ins and API tests · page.route catches requests; fulfill answers them · QuizThe test calls page.goto('/products') and then page.route('**/api/products', …). What happens?B. The page may already have asked the real server before the route existedRoutes only catch requests made after they are set up. The page asks for products while it loads, so register routes first, then navigate.
  47. 14. The network: stand-ins and API tests · Testing what happens when things fail · Fill the blanksAnswer the cart request with one item, before opening the cart.1: route, 2: fulfill, 3: json, 4: gotoRegister the route, answer with fulfill({ json }), and only then navigate so the page’s request is caught.
  48. 14. The network: stand-ins and API tests · Waiting for a request to finish · Find the bugThis test sometimes hangs until it times out. Which line is in the wrong place?Line 2: const ordered = page.waitForResponse('**/api/orders') // before the clickIf the response arrives before waitForResponse starts listening, it is missed, and the wait never ends. Create the promise first, click, then await it.
  49. 14. The network: stand-ins and API tests · Use the API to set up, the browser to check · QuizThe cart is tied to the browser’s session cookie. Which client should add the item?B. page.request: it shares the page’s cookiespage.request (and context.request) use the browser context’s cookies, so the server sees the same session. The request fixture has its own, separate cookies: the item would land in another cart.
  50. 15. Signing in once: authentication and storage state · Wire it up in the config · Put in orderPut what happens on npx playwright test --project=chromium in order.1. The setup project runs auth.setup.ts 2. It signs in and saves the storage state file 3. The chromium tests start, each with a new context 4. Each context is loaded with the saved cookies 5. Each test begins already signed inProject dependencies run first, even when you choose only the dependent project. Each test still gets its own context; the storage state just pre-fills it.
  51. 15. Signing in once: authentication and storage state · Several roles, several files · QuizA chat feature needs a shopper and a staff member at the same time, in one test. How?B. Make two contexts with browser.newContext({ storageState }), one per roleEach context is a separate session, so two contexts in one test can be two different people. Close them at the end. Signing out and in would never have both online at once.
  52. 16. Screenshots and accessibility checks · Practise: screenshots · QuizThe home page shows the current time. Its screenshot test fails on every run. What is the best fix?B. Mask the clock, or fix the time with page.clockThe difference is real but irrelevant, so remove its cause: mask that element, or freeze the page’s time (module 19). A higher tolerance would also hide real changes; updating every run would compare nothing.
  53. 16. Screenshots and accessibility checks · Practise: screenshots · Fill the blanksCompare only the cart summary box, hiding the order number.1: summary, 2: toHaveScreenshot, 3: masktoHaveScreenshot also works on a locator, capturing just that element. Smaller screenshots fail for fewer unrelated reasons.
  54. 16. Screenshots and accessibility checks · What a machine cannot check · Quizaxe reports a contrast problem on a dialog only sometimes. What is the likely cause?B. The check sometimes runs while the dialog is still fading in, half transparentMid-animation, the text really is faint for a moment. Wait until the page is settled (assert the dialog is visible and finished, or turn off animations with reducedMotion: 'reduce' if the site honours it) before running axe. Disabling the rule would hide real problems.
  55. 17. Parallel runs, sharding and CI · A failure restarts the worker · QuizA suite has 2 files of 20 tests each, and 8 workers, with default settings. How many tests can run at once?B. 2By default a file’s tests run in order in one worker, so only the two files run side by side and six workers sit idle. fullyParallel: true lets all 8 workers share the 40 tests.
  56. 17. Parallel runs, sharding and CI · Merging reports from shards · Put in orderPut a sharded CI run in order.1. Four machines each install dependencies and browsers 2. Each runs npx playwright test --shard=N/4 with the blob reporter 3. Each uploads its blob-report folder 4. A final job downloads all four and runs merge-reports 5. The merged HTML report is uploaded for people to readShards run independently and in parallel; only the merge waits for all of them. Upload blobs even when tests fail, or failed shards vanish from the report.
  57. 17. Parallel runs, sharding and CI · Settings that make CI strict and useful · QuizWith retries: 2, a test fails, then passes on its first retry. How is it reported?B. FlakyPlaywright marks it flaky: it passed in the end, but not reliably. The run still succeeds unless failOnFlakyTests is on. Either way, the trace from the retry is there to find out why.
  58. 18. Flaky tests: root causes and real fixes · Cause 1: checking before the page is ready · Find the bugThis test is flaky: sometimes the coupon is not applied. Which line causes it?Line 3: // no branch: await field.fill(…) waits for the fieldisVisible() answers immediately. If the field renders a moment late, the branch is skipped and the coupon never applied. A test should know what the page will show; drop the if and let fill wait.
  59. 18. Flaky tests: root causes and real fixes · Cause 3: tests sharing data on the server · QuizTest A expects the cart to hold 1 item. Test B empties the same account’s cart. They run in parallel. What is the fix?B. Give each test, or each worker, its own account or dataSerial mode hides the problem by giving up parallelism, and retries only lower the odds. Separate data removes the collision entirely. The next module shows per-test and per-worker data fixtures.
  60. 18. Flaky tests: root causes and real fixes · Cause 4: time, randomness and the outside world · QuizA test checks "Ordered today" and fails only on CI runs that start just before midnight. What is the cause?B. The order is placed before midnight and checked after, so it now says "yesterday"The test depends on the real clock. Freezing time with page.clock makes "today" the same throughout, whatever time the run starts.
  61. 18. Flaky tests: root causes and real fixes · Cause 5: a machine with too much to do · Put in orderPut a sound way to deal with a flaky test in order.1. Reproduce it with --repeat-each and no retries 2. Read the failing trace and find what differed 3. Name the cause: timing, data, order, time, or load 4. Fix the cause in the test or the app 5. Repeat the run many times to confirm it is goneGuessing and adding waits rarely works. A trace of the actual failure shows what the page was doing; the repeat run afterwards proves the fix.
  62. 19. Designing a suite that scales · Practise: scopes · QuizWhy can a worker-scoped fixture not ask for page?B. page belongs to one test; a worker fixture outlives many testsA fixture can depend only on fixtures that live at least as long as it does. page is made and thrown away per test; a worker fixture lives for the whole worker. Use browser or playwright there.
  63. 19. Designing a suite that scales · Practise: scopes · Fill the blanksMake the fixture worker-scoped.1: playwright, 2: dispose, 3: scope, 4: workerWorker fixtures are written as [fn, { scope: 'worker' }] and depend on worker-level fixtures like playwright. Disposing the request context after use frees its resources.
  64. 19. Designing a suite that scales · Keeping a large suite fast · QuizA suite takes 40 minutes. Most tests begin by signing in through the form and adding items by clicking. What gives the biggest win first?B. Signing in once with storage state and adding items through the APIRepeated setup through the UI is usually the largest cost, and it is not what those tests check. Lower timeouts make no test faster, only fail sooner; fewer assertions make tests weaker, not quicker.
  65. 20. Mastery challenge · Review: a locator · QuizIn review you see the line below. What is the best feedback?B. Use getByRole('listitem').filter({ hasText: 'Blue teapot' }).getByRole('button', { name: 'Remove' })The CSS path depends on markup and on order, and says nothing about which product or which button. The role-based version names both, survives redesigns and re-sorting, and fails if the button loses its accessible name. force would hide real problems; nth(1) is still order-based.
  66. 20. Mastery challenge · Review: a locator · QuizA test waits for either a success toast or an error alert with expect(toast.or(alert)).toBeVisible(). It sometimes fails with a strict mode violation. Why?B. Both appeared at once, so the locator matched two elementsAssertions like toBeVisible are strict: they need one element. When both show, or matches two. Use .first() if both are legitimate, but here it also hints at a real bug: success and error at the same time.
  67. 20. Mastery challenge · Puzzle: the slow save · Find the bugThis passes locally and fails on CI about one run in ten. Which line is the cause?Line 4: await expect(page.getByRole('status')).toHaveText('Saved')The half-second sleep guesses how long saving takes. On a slow CI machine it is not enough, the reload happens first, and the old name comes back. Wait for the app’s own confirmation (or the save response) before reloading.
  68. 20. Mastery challenge · Puzzle: the slow save · QuizThe page has no element with the text "Loading" at all. What happens?B. It passes straight awaytoBeHidden passes when no element matches or the match is not visible. That is handy for spinners, but also a trap: a typo in the text makes it pass for the wrong reason. Pair it with a positive check of what should appear.
  69. 20. Mastery challenge · Puzzle: a fixture’s cleanup · Find the bugTests using order sometimes find no order to look at. Which line is wrong?Line 6: // move the delete below: await use(order), then deleteCleanup belongs after await use(order). Here the order is deleted before the test ever runs. (It "sometimes" works only if the delete is slow to take effect.)
  70. 20. Mastery challenge · Puzzle: a fixture’s cleanup · QuizA fixture seeds 500 products into a test database and takes 20 seconds. Tests only read them. Which scope?B. Worker scope, or a setup project run once before all testsRead-only data can be shared safely, so make it once: per worker, or once for the whole run in a setup project with dependencies. Per test would add 20 seconds to every test.
  71. 20. Mastery challenge · Puzzle: the mock that does nothing · Find the bugThe page still shows real products, as if the mock were not there. Which line is wrong?Line 1: // register the route first, then: await page.goto('/products')The page fetched its products while loading, before the route existed. Set up routes first, then navigate. (If the app requests /api/products?page=1, the pattern must allow for the query too: a regular expression like /\/api\/products/ does.)
  72. 20. Mastery challenge · Puzzle: the mock that does nothing · QuizEvery checkout test mocks the payment API. A real change to the payment API breaks production, and every test stays green. What would have caught it?B. A few tests, or an API test, against the real (test-mode) payment serviceMocks only check that the app works with the answers you wrote. Something has to check those answers still match reality: a small set of tests against the real service, run regularly.
  73. 20. Mastery challenge · Puzzle: signed out halfway · QuizAll tests share one saved sign-in. One test checks "Sign out". Afterwards, other tests running at the same time fail as signed out. Why?B. Signing out ended the session on the server, and every test was using that same sessionContexts are separate, but they all carry a cookie for the same server session. Signing out ends it for everyone. Give that test its own fresh sign-in (its own account, or its own session) instead of the shared state.
  74. 20. Mastery challenge · Puzzle: signed out halfway · Put in orderOrder the pieces of a sign-in setup for a large, parallel suite.1. A worker fixture creates one test account per worker 2. It signs that account in through the API 3. It saves the storage state to a per-worker file 4. The storageState fixture is overridden to return that file 5. Each test starts signed in as its worker’s own accountThis is the per-worker variant of the setup project: no shared session, no collisions, and sign-in still happens only once per worker. Playwright’s authentication guide shows the same pattern.
  75. 20. Mastery challenge · Puzzle: the CI run · QuizWhich settings belong in a strict, useful CI config?A. forbidOnly: true; B. trace: 'on-first-retry' with a few retries; D. Upload the report even when tests failA stray only, missing traces or a lost report all make failures harder to deal with. Ten retries turns a flaky suite green and hides the problem; keep retries low and treat flaky results as work to do.
  76. 20. Mastery challenge · Puzzle: the CI run · QuizWith --shard=1/4 to 4/4, three shards finish in 3 minutes and one takes 12. The suite is not fullyParallel. What helps most?B. Turn on fullyParallel (or split the huge file) so shards are balanced by testWithout fullyParallel, whole files are shared out, so one giant file lands on one shard. Splitting by test balances the load; more shards would leave that file just as slow.
  77. 20. Mastery challenge · Final: design the test · Fill the blanksA complete test: mock an out-of-stock product, then check the shopper cannot buy it and sees "Sold out".1: route, 2: getByRole, 3: expect, 4: toBeDisabled, 5: Sold outRoute before navigating; find the button by role and name; use web-first assertions for both the disabled button and the message the shopper sees. No sleeps, no CSS, no shared data.
  78. 21. Final project: tests for a room booking app · How the tests reach the app · QuizTwo tests run at once against the one server: one books a meeting, the other checks there are 3 bookings. Why does the second never see the first one’s booking?B. Each test gets a new browser context with no cookies, so the server gives each test its own bookingsThey really do run at once, and nothing undoes a booking. A fresh context per test (module 10) has no cookie, so the server starts it from the samples, apart from everyone else. If the server kept one shared list, contexts alone would not help: that is the shared data of module 18, solved by giving each test its own data (module 19).
  79. 21. Final project: tests for a room booking app · Practise: a locator that lasts · QuizThe next step presses the Book button. Which locator finds it the way a person would, and keeps working when the markup changes?C. page.getByRole('button', { name: 'Book' })A role and a name are what a person, or a screen reader, goes by, and they survive a redesign. getByText('Book') matches part of any text, ignoring case: here the headings Room bookings and Book a room as well as the button, so a click fails with a strict mode violation. form button breaks when the form gains a second button, and nth(0) presses whichever button comes first.
  80. 21. Final project: tests for a room booking app · Step 5: survive a reload · QuizAfter the reload, why check that Planning is visible before checking that 1:1 with Sam is gone?B. Right after a reload the list is still loading and has no rows, so “1:1 with Sam is gone” would pass at once, whatever the server keptCounted straight after reload(), the rows number 0: the page is fetching the list again. toHaveCount(0) does wait, but it is satisfied by that empty moment. Once Planning shows, the list has loaded, and “gone” then means the server really has no 1:1 with Sam.
  81. 21. Final project: tests for a room booking app · Practise: a test that cannot fail · QuizThis passes. Would it fail if the server had a bug and booked Oak at 10:00 a second time?B. No: there are 3 rows before the server has even answered, so the check passes at once and the test endsWith that bug planted in the server, this test still passes, while the step 6 test fails: it waits for the alert, which never comes. A count that should stay the same is true from the start. Wait for the sign that the server has answered first, then check what should not have changed.